feat: add safe_render using Jinja2; add tests

This commit is contained in:
auto-bot 2025-12-24 10:50:34 +08:00
parent a5678de78b
commit ec58508476
2 changed files with 62 additions and 0 deletions

43
app/templates.py Normal file
View File

@ -0,0 +1,43 @@
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)

19
tests/test_templates.py Normal file
View File

@ -0,0 +1,19 @@
import pytest
from app.templates import safe_render
def test_safe_render_legacy_and_nested():
tpl = "收到{actual_ref_amt}元 by {trans_order_info.remark}"
ctx = {"actual_ref_amt": 88.88, "trans_order_info": {"remark": "order123"}}
out = safe_render(tpl, ctx)
assert "88.88" in out
assert "order123" in out
def test_safe_render_missing_var_raises():
tpl = "Hello {missing_var}"
with pytest.raises(Exception):
safe_render(tpl, {})