"""Template engine with variable substitution for prompts.
This module provides template rendering functionality with support for
variable substitution, validation, and variable extraction.
"""
import re
from typing import Any, Dict, List, Tuple
[docs]
def render_template(
template: str,
variables: Dict[str, Any],
delimiters: Tuple[str, str] = ("{{", "}}"),
allow_missing: bool = False,
) -> str:
"""Render a template with variable substitution.
Supports nested variables, conditionals, and loops with simple syntax.
Args:
template (str): Template string with variable placeholders
variables (Dict[str, Any]): Dictionary of variables to substitute
delimiters (Tuple[str, str]): Opening and closing delimiters. Defaults to ("{{", "}}").
allow_missing (bool): If True, missing variables are left as-is. If False, raises KeyError.
Defaults to False.
Returns:
str: Rendered template with variables substituted
Examples:
>>> render_template("Hello {{name}}!", {"name": "Alice"})
'Hello Alice!'
>>> render_template("{{greeting}} {{name}}!", {"greeting": "Hi", "name": "Bob"})
'Hi Bob!'
>>> render_template("Value: {value}", {"value": 42}, delimiters=("{", "}"))
'Value: 42'
"""
if not template:
return template
result = template
open_delim, close_delim = delimiters
# Escape delimiters for regex
open_escaped = re.escape(open_delim)
close_escaped = re.escape(close_delim)
# Find all variable placeholders
pattern = f"{open_escaped}\\s*([\\w.]+)\\s*{close_escaped}"
def replace_variable(match):
var_name = match.group(1).strip()
# Handle nested variables (e.g., user.name)
value = variables
for key in var_name.split("."):
if isinstance(value, dict):
if key in value:
value = value[key]
else:
if allow_missing:
return match.group(0)
raise KeyError(
f"Variable '{var_name}' not found in template variables"
)
else:
if allow_missing:
return match.group(0)
raise KeyError(f"Cannot access '{key}' in non-dict value")
return str(value)
result = re.sub(pattern, replace_variable, result)
return result
[docs]
def render_template_safe(
template: str,
variables: Dict[str, Any],
delimiters: Tuple[str, str] = ("{{", "}}"),
default: str = "",
) -> str:
"""Render a template with safe handling of missing variables.
Missing variables are replaced with the default value instead of raising errors.
Args:
template (str): Template string with variable placeholders
variables (Dict[str, Any]): Dictionary of variables to substitute
delimiters (Tuple[str, str]): Opening and closing delimiters. Defaults to ("{{", "}}").
default (str): Default value for missing variables. Defaults to "".
Returns:
str: Rendered template with variables substituted
Examples:
>>> render_template_safe("Hello {{name}}!", {})
'Hello !'
>>> render_template_safe("Hello {{name}}!", {}, default="[unknown]")
'Hello [unknown]!'
"""
if not template:
return template
result = template
open_delim, close_delim = delimiters
# Escape delimiters for regex
open_escaped = re.escape(open_delim)
close_escaped = re.escape(close_delim)
# Find all variable placeholders
pattern = f"{open_escaped}\\s*([\\w.]+)\\s*{close_escaped}"
def replace_variable(match):
var_name = match.group(1).strip()
# Handle nested variables (e.g., user.name)
value = variables
for key in var_name.split("."):
if isinstance(value, dict) and key in value:
value = value[key]
else:
return default
return str(value)
result = re.sub(pattern, replace_variable, result)
return result
[docs]
def validate_template(
template: str, variables: Dict[str, Any], delimiters: Tuple[str, str] = ("{{", "}}")
) -> Tuple[bool, List[str]]:
"""Validate that all template variables are available.
Args:
template (str): Template string to validate
variables (Dict[str, Any]): Available variables
delimiters (Tuple[str, str]): Opening and closing delimiters. Defaults to ("{{", "}}").
Returns:
Tuple[bool, List[str]]: (is_valid, missing_variables)
Examples:
>>> validate_template("Hello {{name}}!", {"name": "Alice"})
(True, [])
>>> validate_template("Hello {{name}}!", {})
(False, ['name'])
"""
required_vars = extract_template_variables(template, delimiters)
missing = []
for var_name in required_vars:
# Check nested variables
value = variables
keys = var_name.split(".")
found = True
for key in keys:
if isinstance(value, dict) and key in value:
value = value[key]
else:
found = False
break
if not found:
missing.append(var_name)
return len(missing) == 0, missing