Error Handling

Template errors, debugging, and stack traces

5 min read 911 words

Kida provides clear error messages with source locations, error codes, and actionable hints.

Exception Types

Exception When Raised
TemplateError Base class for all template errors
TemplateSyntaxError Invalid template syntax
TemplateRuntimeError Error during template rendering
TemplateNotFoundError Template file not found
UndefinedError Accessing undefined variable (strict mode)

All exception classes are importable from the top-levelkidapackage:

from kida import (
    TemplateError,
    TemplateSyntaxError,
    TemplateRuntimeError,
    TemplateNotFoundError,
    UndefinedError,
)

Syntax Errors

from kida import Environment, TemplateSyntaxError

env = Environment()
try:
    template = env.from_string("{% if x %}")  # Missing end
except TemplateSyntaxError as e:
    print(e)
    # TemplateSyntaxError: Unexpected end of template (expected {% end %})
    # File "<string>", line 1

Error messages include:

  • Error description
  • File name (or<string>for from_string)
  • Line number
  • Relevant source snippet

Undefined Variables

By default, undefined variables raiseUndefinedError:

from kida import UndefinedError

template = env.from_string("{{ missing }}")
try:
    html = template.render()
except UndefinedError as e:
    print(e)
    # UndefinedError: Undefined variable 'missing' in <string>:1

Handle Missing Values

Use thedefaultfilter:

{{ user.nickname | default("Anonymous") }}
{{ config.timeout | default(30) }}

Or check with is defined:

{% if user is defined %}
    {{ user.name }}
{% end %}

Template Not Found

from kida import TemplateNotFoundError

try:
    template = env.get_template("nonexistent.html")
except TemplateNotFoundError as e:
    print(e)
    # TemplateNotFoundError: Template 'nonexistent.html' not found in: templates/

Runtime Errors

Python errors during rendering include template context:

template = env.from_string("{{ items[10] }}")
try:
    html = template.render(items=[1, 2, 3])
except IndexError as e:
    # Traceback shows template location
    pass

Debug Filter

Inspect values during development:

{{ posts | debug }}
{{ posts | debug("my posts") }}

Output (to stderr):

DEBUG [my posts]: <list[5]>
  [0] Post(title='First Post', weight=10)
  [1] Post(title='Second', weight=None)  <-- None: weight
  ...

Error Codes

Every Kida exception carries anErrorCodethat categorizes the error and links to documentation:

Code Category Description
K-LEX-001 Lexer Unterminated string literal
K-LEX-002 Lexer Invalid character in template
K-PAR-001 Parser Unexpected token
K-PAR-002 Parser Unclosed block tag
K-PAR-003 Parser Invalid expression
K-RUN-001 Runtime Undefined variable
K-RUN-002 Runtime Type error in expression
K-RUN-003 Runtime Index out of range
K-RUN-004 Runtime Filter execution error
K-TPL-001 Template Template not found
K-TPL-002 Template Template syntax error
K-TPL-003 Template Circular macro import

Access the code programmatically:

from kida import UndefinedError, ErrorCode

try:
    template.render()
except UndefinedError as e:
    print(e.code)        # ErrorCode.K_RUN_001
    print(e.code.value)  # "K-RUN-001"

Compact Error Format

All Kida exceptions provide aformat_compact()method that produces a structured, human-readable summary for terminal output or logging:

from kida import TemplateError

try:
    template.render()
except TemplateError as e:
    print(e.format_compact())

Output:

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

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

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

The compact format includes:

  • Error code and description
  • Source snippet with line numbers and error pointer
  • Hint with suggestions (typo corrections,defaultfilter usage)
  • Docs link to the relevant error code documentation

This is the recommended format for frameworks that wrap Kida errors (like Chirp and Bengal).

Source Snippets

For programmatic access to the source context around an error, use thesource_snippetattribute:

from kida import UndefinedError, SourceSnippet

try:
    template.render()
except UndefinedError as e:
    snippet: SourceSnippet | None = e.source_snippet
    if snippet:
        print(snippet.lines)       # List of (line_number, line_text) tuples
        print(snippet.error_line)  # The line number with the error
        print(snippet.filename)    # Template filename

You can also build snippets manually with build_source_snippet():

from kida import build_source_snippet

snippet = build_source_snippet(source_text, error_line=42, context_lines=3)

Common Error Patterns

Missing Variable

UndefinedError: Undefined variable 'usre' in page.html:5

Fix: Check for typos, ensure variable is passed to render().

Attribute Error

UndefinedError: 'dict' object has no attribute 'nmae'

Fix: Check attribute spelling, verify object type.

Type Error in Filter

TypeError: object of type 'NoneType' has no len()

Fix: Usedefaultfilter or check for None.

{{ items | default([]) | length }}

Template Not Found

TemplateNotFoundError: Template 'bsae.html' not found

Fix: Check file path, verify loader configuration.

Error Handling in Production

from kida import TemplateError

def render_page(template_name, **context):
    try:
        return env.render(template_name, **context)
    except TemplateError as e:
        # Log the compact format for clean terminal output
        logger.error("Template error:\n%s", e.format_compact())
        # Return fallback
        return env.render("error.html", error=str(e))

Best Practices

Validate Context

def render_safely(template, **context):
    # Validate required fields
    required = ["title", "user"]
    missing = [k for k in required if k not in context]
    if missing:
        raise ValueError(f"Missing context: {missing}")

    return template.render(**context)

Use Default Values

{# Defensive defaults #}
{{ user.name | default("Guest") }}
{{ items | default([]) | length }}
{{ config.get("timeout", 30) }}

Check Before Access

{% if user and user.profile %}
    {{ user.profile.bio }}
{% end %}

See Also