44 lines
1.5 KiB
Python
44 lines
1.5 KiB
Python
import re
|
|
from typing import Any, Dict
|
|
|
|
import jinja2
|
|
|
|
# Create a minimal Jinja2 environment for text templates.
|
|
# Use StrictUndefined so missing variables raise exceptions during rendering.
|
|
ENV = jinja2.Environment(
|
|
undefined=jinja2.StrictUndefined,
|
|
autoescape=False,
|
|
)
|
|
|
|
|
|
def _convert_braced_format_to_jinja(template: str) -> str:
|
|
"""
|
|
Convert simple `{var}` style placeholders into Jinja `{{ var }}` so legacy templates keep working.
|
|
This will not touch existing `{{ }}` or `{% %}`.
|
|
"""
|
|
# Skip conversion if template already contains jinja markers
|
|
if ("{{" in template) or ("{%" in template):
|
|
return template
|
|
|
|
# Replace single-brace simple identifiers like {a_b} or {a.b} -> {{ a_b }} or {{ a.b }}
|
|
pattern = re.compile(r'(?<!\{)\{([A-Za-z0-9_\.]+)\}(?!\})')
|
|
return pattern.sub(r'{{ \1 }}', template)
|
|
|
|
|
|
def safe_render(template_str: str, context: Dict[str, Any]) -> str:
|
|
"""
|
|
Safely render a template string using Jinja2 with StrictUndefined.
|
|
- Accepts legacy `{var}` placeholders by converting them to Jinja style.
|
|
- Raises jinja2 exceptions when rendering fails.
|
|
"""
|
|
if template_str is None:
|
|
raise ValueError("template_str is None")
|
|
|
|
tpl = _convert_braced_format_to_jinja(template_str)
|
|
jtpl = ENV.from_string(tpl)
|
|
# Jinja expects normal Python types in context; if context contains Pydantic models,
|
|
# they should be converted by caller to plain dicts.
|
|
return jtpl.render(**context)
|
|
|
|
|