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