Errors

Error hierarchy, error handlers, and debug pages

4 min read 773 words

Error Hierarchy

flowchart TD ChirpError --> ConfigurationError["ConfigurationError\nInvalid config (missing secret_key, etc.)"] ChirpError --> HTTPError["HTTPError\nHTTP-level errors"] HTTPError --> NotFound["NotFound — 404"] HTTPError --> MethodNotAllowed["MethodNotAllowed — 405"]

All Chirp exceptions inherit fromChirpError. HTTP errors carry a status code and detail message.

Raising Errors

Raise HTTP errors in route handlers to trigger error responses:

from chirp import NotFound, HTTPError

@app.route("/users/{id:int}")
async def get_user(id: int):
    user = await db.fetch_one("SELECT * FROM users WHERE id = ?", [id])
    if not user:
        raise NotFound(f"User {id} not found")
    return Template("user.html", user=user)

@app.route("/premium")
def premium():
    if not g.user or not g.user.is_premium:
        raise HTTPError(403, "Premium access required")
    return Template("premium.html")

NotFound

raise NotFound("Page not found")     # 404 with detail message
raise NotFound()                       # 404 with default message

MethodNotAllowed

Raised automatically by the router when a path matches but the HTTP method does not. The response includes anAllowheader listing valid methods and the allowed methods in the body.

ConfigurationError

Raised at startup for invalid configuration:

# These raise ConfigurationError:
# - Using SessionMiddleware without secret_key
# - Using CSRFMiddleware without secret_key
# - Returning Template without kida integration configured

Error Handlers

Register custom error handlers by status code or exception type:

@app.error(404)
def handle_404(request: Request):
    return Template("errors/404.html", path=request.path)

@app.error(500)
def handle_500(request: Request, error: Exception):
    return Template("errors/500.html", error=str(error))

Error handlers support the same return-value system as route handlers. You can return a Template, Fragment, Response, string, or dict.

Handler Signatures

Error handlers support flexible signatures:

# Zero arguments
@app.error(404)
def handle_404():
    return "Not Found"

# Request only
@app.error(404)
def handle_404(request: Request):
    return Template("404.html", path=request.path)

# Request and error
@app.error(500)
def handle_500(request: Request, error: Exception):
    log_error(error)
    return Template("500.html")

Chirp inspects the handler signature and injects the appropriate arguments.

Exception Type Handlers

Handle specific exception types:

from chirp import ValidationError

@app.error(ValidationError)
def handle_validation(request: Request, error: ValidationError):
    return Response(str(error)).with_status(422)

Fragment-Aware Error Handling

When an htmx request triggers an error, Chirp renders error fragments instead of full error pages:

@app.error(404)
def handle_404(request: Request):
    if request.is_fragment:
        return Fragment("errors/404.html", "error_message", path=request.path)
    return Template("errors/404.html", path=request.path)

Built-in error handling automatically returns <div class="chirp-error">snippets for htmx requests.

Terminal Error Formatting

During development, Chirp formats errors for the terminal with structured, readable output instead of raw Python tracebacks.

Template Errors

When a Kida template error occurs, Chirp displays the error with its code, source snippet, and route context:

-- Template Error -----------------------------------------------
K-RUN-001: Undefined variable 'usernme' in base.html:42

     |
> 42 | <h1>{{ usernme }}</h1>
     |

Hint: Did you mean 'username'?
Docs: https://kida.dev/docs/errors/#k-run-001

  Route: GET /dashboard
-----------------------------------------------------------------

Non-Template Errors

For other exceptions, Chirp filters tracebacks to show only application frames (hiding framework and stdlib internals).

Traceback Verbosity

Control terminal traceback style with theCHIRP_TRACEBACKenvironment variable:

Value Behavior
compact App frames only (default)
full Full Python traceback
minimal Single-line error summary
# Show full tracebacks during deep debugging
CHIRP_TRACEBACK=full chirp run myapp:app

# Minimal output for CI/production
CHIRP_TRACEBACK=minimal chirp run myapp:app

Streaming and SSE Errors

Errors during chunked streaming or SSE connections use the same formatting pipeline. In debug mode, streaming errors render as a visible<div class="chirp-error">element in the page.

SSE streams have per-event error boundaries: if a singleFragment fails to render, the error is caught and the stream continues. In debug mode, the failed block is replaced with a <div class="chirp-block-error">element showing the exception inline. In production, the event is silently skipped.

If thecontext_builder() in a reactive stream raises, the entire event is skipped (logged via chirp.reactivelogger) and the stream waits for the next change.

Catastrophic errors (ASGI failures, unrecoverable state) still terminate the stream with anevent: errormessage the client can handle.

Debug Pages

WhenAppConfig(debug=True), unhandled exceptions render a detailed debug page with:

  • Full traceback with source code context (framework frames collapsed)
  • Request details (method, path, headers, query parameters)
  • Application configuration
  • Kida template error panel with error code, source snippet, suggestion, and docs link
  • Environment section — Python, Chirp, and Kida versions

When a Kida parse error is wrapped (e.g. byTemplateRuntimeError), the debug page prefers the cause's template context (correct file and line) over the wrapper's. Consecutive framework frames (middleware, ASGI adapters) are collapsed into an expandable block to reduce noise.

Warning

Debug pages expose internal details. Never enabledebug=Truein production.

Next Steps