▶ runtime-narrative

Quick Start

Wrap a block of code in story(), break it into stage() steps, and let ConsoleRenderer narrate what happened — no configuration required.

Context manager API — success path

The most direct way to use runtime-narrative: open a story(), then nest stage() blocks inside it for each step you want tracked.

python
from runtime_narrative import story, stage

with story("Import Customers", total_stages=3):
    with stage("Load CSV"):
        rows = ["alice", "bob", "carol"]

    with stage("Validate Data"):
        if not rows:
            raise ValueError("No rows found")

    with stage("Insert Records"):
        print(f"Inserted {len(rows)} records")

The total_stages=3 hint lets renderers show accurate progress percentages. Without it, the runtime infers total stages from how many stages have been registered so far, which means percentages are always 100% at completion rather than showing intermediate progress.

ConsoleRenderer is the default renderer and needs no configuration. On failure it prints the exact frame, a source snippet, the exception chain, and a compressed stack summary. When nothing fails, it narrates each stage as it starts and completes.

Decorator API — failure path

Prefer keeping the story/stage boundary next to the function definition rather than at the call site? Use @runtime_narrative_story and @runtime_narrative_stage instead. This example lets an exception in the last stage propagate, triggering FailureOccurred.

python
from runtime_narrative import runtime_narrative_story, runtime_narrative_stage

@runtime_narrative_stage("Load CSV")
def load_csv() -> list[str]:
    return ["alice", "bob"]

@runtime_narrative_stage("Validate Data")
def validate(rows: list[str]) -> None:
    if not rows:
        raise ValueError("No rows found")

@runtime_narrative_stage("Insert Records")
def insert(rows: list[str]) -> None:
    raise ValueError("duplicate customer id")  # triggers FailureOccurred

@runtime_narrative_story("Import Customers")
def run() -> None:
    rows = load_csv()
    validate(rows)
    insert(rows)

try:
    run()
except Exception:
    pass

Both APIs produce identical event streams

The context-manager form and the decorator form emit the same StoryStarted, StageStarted, StageCompleted, FailureOccurred, and StoryCompleted events. Choose whichever fits your call site — decorators are convenient when the instrumented functions live in different modules.

Async context manager

story and stage are dual sync/async: async with works identically for async code, using the same names.

python
import asyncio
from runtime_narrative import story, stage

async def main():
    async with story("Async Pipeline"):
        async with stage("Fetch Data"):
            await asyncio.sleep(0.1)

        async with stage("Process"):
            pass

asyncio.run(main())

Next step

Read Core Concepts for the mental model behind stories, stages, events, and renderers, or jump straight to story() for the full constructor reference.