On this page

Scoped cleanup lesser conduits๏ƒ

๐ŸŸก Intermediate ยท Lesson 29

Scopes that END. A lesser conduit is a scope you can THROW AWAY: build one for a job, meld what the job needs, then cleanup() the child - its per-conduit creations are disposed through the book's disposal vocabulary, and the root keeps working untouched. THE LIFECYCLE LAW: with a DI runtime YOU own the lifecycles. The runtime holds references to what it built, so the GC cannot free what the world still holds - cleanup() is how memory comes back. (Beginner lesson 41 proves this with a weakref; here it becomes a working pattern.)

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/29_scoped_cleanup_lesser_conduits.py
py -3.14t UX_and_AIX_experiences/02_intermediate/29_scoped_cleanup_lesser_conduits.py

Download this collection ยท Source on GitHub

Public surface๏ƒ

create_lesser_conduit as a disposable scope, conduit.cleanup() on the child only, disposal_method_names firing scope-locally

Code๏ƒ

 1"""
 2TIER: intermediate (29)
 3GOAL: Scopes that END. A lesser conduit is a scope you can THROW AWAY:
 4      build one for a job, meld what the job needs, then cleanup() the
 5      child - its per-conduit creations are disposed through the book's
 6      disposal vocabulary, and the root keeps working untouched.
 7      THE LIFECYCLE LAW: with a DI runtime YOU own the lifecycles. The
 8      runtime holds references to what it built, so the GC cannot free
 9      what the world still holds - cleanup() is how memory comes back.
10      (Beginner lesson 41 proves this with a weakref; here it becomes a
11      working pattern.)
12SURFACE EXERCISED: create_lesser_conduit as a disposable scope,
13                   conduit.cleanup() on the child only,
14                   disposal_method_names firing scope-locally
15"""
16import sys
17from pathlib import Path
18
19sys.path.insert(0, str(Path(__file__).parent))  # local helper (see _dynamic_world)
20from _dynamic_world import dynamic_spellbook
21
22import melder as md
23
24
25class JobSession:
26    def __init__(self) -> None:
27        self.closed = False
28
29    def close(self) -> None:
30        self.closed = True
31
32
33def main() -> None:
34    book = md.Spellbook()
35    book.bind(spell=JobSession, existence="unique_per_conduit",
36              disposal_method_names=["close"])
37    root = book.conjure()
38
39    # The long-lived scope holds its own session...
40    root_session = root.meld(spell=JobSession)
41
42    # ...and each job gets a THROWAWAY scope with its own.
43    job = root.create_lesser_conduit()
44    job_session = job.meld(spell=JobSession)
45    assert job_session is not root_session  # per-conduit = per-scope
46
47    # Job done: end the SCOPE, not the world.
48    job.cleanup()
49    print("job scope closed its session:", job_session.closed)
50    print("root session untouched:", root_session.closed is False)
51
52    # The root never noticed - it keeps resolving.
53    assert root.meld(spell=JobSession) is root_session
54    print("root still resolves after the child scope ended")
55
56    # And when the whole world is done, the same verb one level up.
57    root.cleanup()
58    print("root cleanup closed its session too:", root_session.closed)
59
60
61if __name__ == "__main__":
62    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