On this page
Build a complete beginner application๏
Build a small orders application with ordinary Python objects, one bootstrap, and a separate module that uses the running graph. The configuration and pool are shared; each request receives a fresh handler.
Give each module one job๏
Keep these four files together in the saved Beginner collection:
01_beginner/
โโโ capstone_models.py # Your application objects
โโโ capstone_bootstrap.py # Register objects and conjure once
โโโ capstone_application.py # Resolve typed objects and use them
โโโ 40_beginner_capstone.py # Start, run, and clean up
The entry point calls the bootstrap, passes the returned conduit to the
application, and owns shutdown. The application module receives a usable
conduit and works with the objects returned by meld().
All four modules run in one Python process. Importing capstone_bootstrap
loads the build_application function definition. The graph is created when
main() calls that function:
build_application()creates the book, binds the classes, conjures, and returns both objects.main()receives them inbook, conduit.run_application(conduit)passes that same conduit object into the application function.The function uses its
conduitparameter to resolve application objects.When it returns,
main()owns shutdown.
Define ordinary application objects๏
capstone_models.py contains configuration, a small resource, and a request
handler. DbPool uses an in-memory order store to demonstrate a pool's lifetime;
it opens no database connection. The handler uses its injected configuration
and pool to answer a request.
The constructor's AppConfig and DbPool annotations refer to real classes
available in this module. Melder can inspect those dependencies when building
the handler. The handler borrows the pool; the conduit owns its disposal.
"""Ordinary application objects for the beginner capstone; this module does not import Melder."""
class AppConfig:
"""Carry the application's name as ordinary configuration data."""
def __init__(self, app_name: str = "orders-service") -> None:
"""Store the application's default or supplied name; own no external resources."""
self.app_name = app_name
class DbPool:
"""Demonstrate a shared resource with a small in-memory order store.
No database connection is opened. The conduit owns this pool's lifetime.
Closing deletes the store; closed/query_count remain readable for the
example's shutdown checks. This single-threaded example does not promise
concurrent access to the application-owned store.
"""
def __init__(self) -> None:
"""Create the owned order store and initialize observable lifecycle/query state."""
self._orders = {101: "coffee", 102: "tea", 103: "cocoa"}
self.closed = False
self.query_count = 0
def close(self) -> None:
"""Release the store once; repeated closes preserve the recorded shutdown state."""
if self.closed:
return
del self._orders
self.closed = True
def fetch_order(self, order_id: int) -> str:
"""Return one order and count the query; reject closed pools or unknown order IDs.
Raises:
RuntimeError: The pool has already closed.
KeyError: The order ID does not exist in this demonstration store.
"""
if self.closed:
raise RuntimeError("The order pool is closed; run requests before application shutdown.")
order = self._orders[order_id]
self.query_count += 1
return order
class RequestHandler:
"""Handle one request using borrowed configuration and a borrowed shared pool.
Constructor annotations name real runtime classes so Melder can inspect
them for dependency injection. The handler does not own or close the pool.
"""
def __init__(self, config: AppConfig, pool: DbPool) -> None:
"""Borrow the injected dependencies and start this handler's independent request count."""
self._config = config
self._pool = pool
self.requests_handled = 0
def handle(self, order_id: int) -> str:
"""Fetch an order and return its application-facing message.
A successful request increments this handler's count. Pool lookup or
closed-state errors propagate to the caller without counting a request.
"""
order = self._pool.fetch_order(order_id)
self.requests_handled += 1
return f"{self._config.app_name}: order {order_id} = {order}"
Bootstrap with direct binding calls๏
The bootstrap imports the actual classes, registers them, and conjures once.
Each bind() manages its own transaction and synchronization. Ordinary binding
needs no outer with book: block; these are individual registration calls.
Registration |
Lifetime |
Result |
|---|---|---|
The |
|
Every handler receives one shared configuration object |
The |
|
All requests share one pool; cleanup calls |
The |
|
Every meld creates a fresh handler with injected dependencies |
The configuration uses its ordinary Python constructor default. The bootstrap registers its class, and Melder creates the shared object when it is first resolved.
"""Own registration and startup for the beginner application."""
import melder as md
from capstone_models import AppConfig, DbPool, RequestHandler
def build_application() -> tuple[md.Spellbook, md.Conduit]:
"""Register the graph and return the book/conduit to the entry point that owns shutdown.
Config and pool are shared; handlers are created per meld. Registration or
conjure failure cleans the partially configured book and propagates the error.
"""
book = md.Spellbook()
try:
# Each bind manages its own transaction and synchronization.
book.bind(spell=AppConfig, existence="unique")
book.bind(spell=DbPool, existence="unique", disposal_method_names=["close"])
book.bind(spell=RequestHandler, existence="many")
conduit = book.conjure()
except Exception:
book.cleanup()
raise
return book, conduit
Resolve typed objects in the consuming module๏
This consumer uses md, AppConfig, DbPool, and RequestHandler only in
type annotations, so those imports live under if TYPE_CHECKING:.
Runtime work uses conduit.meld() on the object passed by main().
The bootstrap imports Melder normally because it calls md.Spellbook(),
and imports the real application classes to register them.
In def run_application(conduit: md.Conduit), conduit is the parameter
receiving the object from main(). md.Conduit is its type annotation.
Writing only conduit would still allow Python to execute the function; the
annotation supplies type information for editors and checkers.
Python 3.14 defers function annotations. This application uses the hints for editors and type checkers and does not evaluate them during execution.
The runtime lookup is explicit: spell="RequestHandler" names the registered
spell. The annotation handler: RequestHandler describes the returned object
to your tools. It does not construct, convert, or wrap that object.
The bootstrap still imports and binds the real classes. This separates startup from consumption while keeping the consumer's object types precise.
"""Consume the running graph using concrete types without runtime model imports."""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import melder as md
from capstone_models import AppConfig, DbPool, RequestHandler
def run_application(conduit: md.Conduit) -> list[str]:
"""Resolve and use application objects; borrow the conduit without owning its cleanup.
TYPE_CHECKING supplies editor/checker types. The strings passed to meld are
the runtime addresses registered by the bootstrap. Return the three request
messages, with assertions that demonstrate shared and fresh lifetimes.
"""
config: AppConfig = conduit.meld(spell="AppConfig")
pool: DbPool = conduit.meld(spell="DbPool")
assert config is conduit.meld(spell="AppConfig")
assert pool is conduit.meld(spell="DbPool")
handlers: list[RequestHandler] = []
messages: list[str] = []
for order_id in (101, 102, 103):
handler: RequestHandler = conduit.meld(spell="RequestHandler")
assert handler.requests_handled == 0
messages.append(handler.handle(order_id))
assert handler.requests_handled == 1
handlers.append(handler)
assert handlers[0] is not handlers[1] and handlers[1] is not handlers[2]
assert handlers[0] is not handlers[2]
assert pool.query_count == 3
return messages
The assertions show that configuration and pool are shared, handlers are distinct, and their work reaches the same pool. The handler calls real methods on its dependencies and returns an application result.
Run the application and own shutdown๏
The entry point starts the graph, calls the application, and prints its results.
Its finally blocks guarantee that both cleanup calls are attempted if the
application raises. The pool retains a closed flag so this demonstration can
check that the configured disposal method ran.
"""
TIER: beginner (40) - capstone
GOAL: Build and use a small application across separate Python modules:
ordinary objects, one bootstrap, a TYPE_CHECKING consumer, constructor
injection, shared resources, fresh handlers, and explicit cleanup.
Each bind manages its own transaction; no outer book lock is needed.
SURFACE EXERCISED: md.Spellbook.bind/conjure, md.Conduit.meld/cleanup,
unique/many, constructor injection, disposal_method_names
"""
from typing import TYPE_CHECKING
from capstone_application import run_application
from capstone_bootstrap import build_application
if TYPE_CHECKING:
from capstone_models import DbPool
def main() -> None:
"""Start the graph, use it, and guarantee shutdown around the application call."""
book, conduit = build_application()
try:
pool: DbPool = conduit.meld(spell="DbPool")
messages = run_application(conduit)
assert messages == [
"orders-service: order 101 = coffee",
"orders-service: order 102 = tea",
"orders-service: order 103 = cocoa",
]
for message in messages:
print(message)
finally:
try:
conduit.cleanup()
finally:
book.cleanup()
assert pool.closed and pool.query_count == 3
print("pool closed:", pool.closed)
print("capstone complete: bootstrapped, typed, injected, used, cleaned")
if __name__ == "__main__":
main()
From the repository root with Melder installed and Python 3.14 free-threading selected:
python UX_and_AIX_experiences/01_beginner/40_beginner_capstone.py
On Windows, select the free-threaded interpreter explicitly:
py -3.14t UX_and_AIX_experiences/01_beginner/40_beginner_capstone.py
Expected application output after the assertions pass:
orders-service: order 101 = coffee
orders-service: order 102 = tea
orders-service: order 103 = cocoa
pool closed: True
capstone complete: bootstrapped, typed, injected, used, cleaned
Download the Beginner collection with all four files.
Keep the sibling modules beside the entry script when extracting or copying it.
Continue to Intermediate for more configuration and cooperation between independently owned subsystems. The bootstrap-pattern and inventory-pattern lessons below provide other useful ways to organize and inspect an application.
Runnable examples๏
Beginner capstone โ Beginner 40
Bootstrap pattern โ Beginner 18
Agent inventory pattern โ Beginner 39