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:

  1. build_application() creates the book, binds the classes, conjures, and returns both objects.

  2. main() receives them in book, conduit.

  3. run_application(conduit) passes that same conduit object into the application function.

  4. The function uses its conduit parameter to resolve application objects.

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

capstone_models.py๏ƒ
"""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 AppConfig class

unique

Every handler receives one shared configuration object

The DbPool class

unique

All requests share one pool; cleanup calls close()

The RequestHandler class

many

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.

capstone_bootstrap.py๏ƒ
"""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.

capstone_application.py๏ƒ
"""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.

40_beginner_capstone.py๏ƒ
"""
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๏ƒ

All beginner examples ยท Level contents ยท Full contents

Canonical page source