On this page

Managed spell injection and request scopes๏ƒ

๐ŸŸก Intermediate ยท Lesson 47

Borrow a pooled object per request, give it back when the request ends. A managed spell can be injected like any other: annotate a constructor parameter with its class. Every injection is its own lease, so two handlers get two objects. The holder gives its object back - here the handler's own disposal method releases it, and the handler lives in a request scope (a lesser conduit), so leaving the scope disposes the handler and the connection returns to the ROOT's pool for the next request.

The pool belongs to the root and outlives every request scope. Root teardown disposes the idle objects only: an object still out is its holder's, and a release after teardown is refused because the pool left with its root. So give leases back before the root ends - a consumer melded on the root itself that releases at its own disposal meets a pool already gone at root teardown.

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

Download this collection ยท Source on GitHub

Public surface๏ƒ

md.Spellbook.bind, md.Existence.managed_spell_quantity, md.Existence.many (disposal_method_names), constructor injection, md.Conduit.enter_lesser_conduit, md.Conduit.meld, md.ReleaseManagedSpell (release, describe), md.Conduit.cleanup

Code๏ƒ

  1"""
  2TIER: intermediate (47)
  3GOAL: Borrow a pooled object per request, give it back when the request ends.
  4      A managed spell can be injected like any other: annotate a constructor parameter
  5      with its class. Every injection is its own lease, so two handlers get two
  6      objects. The holder gives its object back - here the handler's own disposal
  7      method releases it, and the handler lives in a request scope (a lesser conduit),
  8      so leaving the scope disposes the handler and the connection returns to the
  9      ROOT's pool for the next request.
 10
 11      The pool belongs to the root and outlives every request scope. Root teardown
 12      disposes the idle objects only: an object still out is its holder's, and a
 13      release after teardown is refused because the pool left with its root. So give
 14      leases back before the root ends - a consumer melded on the root itself that
 15      releases at its own disposal meets a pool already gone at root teardown.
 16SURFACE EXERCISED: md.Spellbook.bind, md.Existence.managed_spell_quantity,
 17                   md.Existence.many (disposal_method_names), constructor injection,
 18                   md.Conduit.enter_lesser_conduit, md.Conduit.meld,
 19                   md.ReleaseManagedSpell (release, describe), md.Conduit.cleanup
 20"""
 21from typing import Tuple
 22
 23import melder as md
 24
 25
 26class PooledConnection:
 27    """A connection the root keeps pooled across requests; `close` disposes it."""
 28
 29    def __init__(self) -> None:
 30        """Open the connection."""
 31        self.closed: bool = False
 32
 33    def close(self) -> None:
 34        """Close the connection; called when the pool destroys it."""
 35        self.closed = True
 36
 37
 38class RequestHandler:
 39    """Handles one request with a pooled connection injected into its constructor.
 40
 41    `finish` is its disposal method: the request scope runs it when the scope ends,
 42    and it hands the connection back to the root's pool exactly once.
 43    """
 44
 45    def __init__(self, connection: PooledConnection) -> None:
 46        """Keep the leased connection for the life of the request."""
 47        self.connection: PooledConnection = connection
 48        self.finished: bool = False
 49
 50    def finish(self) -> None:
 51        """Give the connection back; later calls do nothing."""
 52        if self.finished:
 53            return
 54        self.finished = True
 55        md.ReleaseManagedSpell().release(self.connection)
 56
 57
 58def pool_counts(cls: type) -> Tuple[int, int, int]:
 59    """Return (alive, idle, out) for the one pool of a managed class."""
 60    (snapshot,) = md.ReleaseManagedSpell().describe(cls)
 61    return snapshot["count"], snapshot["idle_count"], snapshot["out_count"]
 62
 63
 64def main() -> None:
 65    """Serve two requests from one pool, then tear the root down.
 66
 67    Contract:
 68        Checks that each injection leases its own object, that ending a request
 69        scope returns its leases, that the next request reuses them, and what root
 70        teardown does to idle and outstanding objects.
 71    Returns:
 72        None.
 73    """
 74    book = md.Spellbook()
 75    try:
 76        book.bind(
 77            spell=PooledConnection,
 78            existence=md.Existence.managed_spell_quantity,
 79            disposal_method_names=["close"],
 80        )
 81        book.bind(spell=RequestHandler, existence=md.Existence.many, disposal_method_names=["finish"])
 82        root = book.conjure()
 83        try:
 84            with root.enter_lesser_conduit() as request:
 85                first: RequestHandler = request.meld("RequestHandler")
 86                second: RequestHandler = request.meld("RequestHandler")
 87                assert first.connection is not second.connection
 88                assert pool_counts(PooledConnection) == (2, 0, 2)
 89                print("two handlers, two leased connections (alive, idle, out):", pool_counts(PooledConnection))
 90            # Leaving the request scope disposed both handlers; each released its connection.
 91            assert first.finished and second.finished
 92            assert pool_counts(PooledConnection) == (2, 2, 0)
 93            print("the request ended and both connections went back:", pool_counts(PooledConnection))
 94
 95            with root.enter_lesser_conduit() as request:
 96                third: RequestHandler = request.meld("RequestHandler")
 97                assert third.connection in (first.connection, second.connection)
 98                assert pool_counts(PooledConnection) == (2, 1, 1)
 99                print("the next request reused a pooled connection:", True)
100            assert pool_counts(PooledConnection) == (2, 2, 0)
101
102            # Keep one connection out across root teardown.
103            outstanding: PooledConnection = root.meld("PooledConnection")
104            assert pool_counts(PooledConnection) == (2, 1, 1)
105        finally:
106            root.cleanup()
107
108        # Teardown closed the idle connection, left the outstanding one to its holder...
109        idle = second.connection if outstanding is first.connection else first.connection
110        assert idle.closed and not outstanding.closed
111        print("root teardown closed the idle connection, not the one still out:", not outstanding.closed)
112        # ...and the pool is gone, so giving it back now is refused.
113        try:
114            md.ReleaseManagedSpell().release(outstanding)
115            raise AssertionError("a connection was released into a torn-down root")
116        except ValueError as refused:
117            outstanding.close()  # it is ours to dispose now
118            print("a release after root teardown is refused:", str(refused)[:70], "...")
119    finally:
120        book.cleanup()
121
122
123if __name__ == "__main__":
124    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๏ƒ