Release govoplan-templates v0.1.22: bound rendering and improve dialog layout

This commit is contained in:
2026-09-08 01:32:53 +02:00
parent 69416faca9
commit 19cbdd303b
9 changed files with 172 additions and 42 deletions
+61 -17
View File
@@ -362,22 +362,49 @@ def _render_payload(
) -> tuple[bytes, str, int]:
if request.output_format == "text":
body = revision.content_text or _html_to_text(revision.content_html or "")
rendered = [
_substitute(body, _render_context(request.parameters, item, index), html=False)
for index, item in enumerate(items)
]
separator = "\n\n---\n\n" if revision.template_type != "list_layout" else "\n"
rendered = _render_items(body, request.parameters, items, html=False, separator=separator)
payload = separator.join(rendered).encode("utf-8")
return payload, "text/plain; charset=utf-8", _page_count(revision, len(items))
body = revision.content_html or f"<pre>{escape(revision.content_text or '')}</pre>"
rendered = [
_substitute(body, _render_context(request.parameters, item, index), html=True)
for index, item in enumerate(items)
]
rendered = _render_items(body, request.parameters, items, html=True)
page_count = _page_count(revision, len(items))
document = _html_document(definition, revision, rendered)
return document.encode("utf-8"), "text/html; charset=utf-8", page_count
payload = document.encode("utf-8")
if len(payload) > MAX_OUTPUT_BYTES:
_output_limit_exceeded()
return payload, "text/html; charset=utf-8", page_count
def _output_limit_exceeded() -> None:
raise TemplateRenderError(
f"Rendered output exceeds the {MAX_OUTPUT_BYTES} byte bounded-download limit."
)
def _render_items(
body: str,
parameters: Mapping[str, object],
items: Sequence[Mapping[str, object]],
*,
html: bool,
separator: str = "",
) -> list[str]:
# Reject as soon as the same existing output budget is exhausted; never
# build thousands of oversized documents and only then measure the join.
remaining = MAX_OUTPUT_BYTES
separator_bytes = len(separator.encode("utf-8"))
rendered: list[str] = []
for index, item in enumerate(items):
if index:
remaining -= separator_bytes
if remaining < 0:
_output_limit_exceeded()
value = _substitute(body, _render_context(parameters, item, index), html=html, max_bytes=remaining)
remaining -= len(value.encode("utf-8"))
rendered.append(value)
return rendered
def _html_document(
@@ -552,15 +579,32 @@ def _render_context(
}
def _substitute(template: str, context: Mapping[str, object], *, html: bool) -> str:
def replacement(match: re.Match[str]) -> str:
value, present = _resolve_path(context, match.group(1))
if not present or value is None:
return ""
rendered = _display_value(value)
return escape(rendered, quote=True) if html else rendered
def _substitute(template: str, context: Mapping[str, object], *, html: bool, max_bytes: int | None = None) -> str:
remaining = MAX_OUTPUT_BYTES if max_bytes is None else max_bytes
pieces: list[str] = []
return _TOKEN_PATTERN.sub(replacement, template)
def append(value: str) -> None:
nonlocal remaining
# Character count is a cheap lower bound before allocating UTF-8 bytes.
if len(value) > remaining:
_output_limit_exceeded()
remaining -= len(value.encode("utf-8"))
if remaining < 0:
_output_limit_exceeded()
pieces.append(value)
previous = 0
for match in _TOKEN_PATTERN.finditer(template):
append(template[previous:match.start()])
value, present = _resolve_path(context, match.group(1))
if present and value is not None:
rendered = _display_value(value)
if len(rendered) > remaining:
_output_limit_exceeded()
append(escape(rendered, quote=True) if html else rendered)
previous = match.end()
append(template[previous:])
return "".join(pieces)
def _resolve_path(context: Mapping[str, object], path: str) -> tuple[object | None, bool]: