83:def validate_html(html: str) -> List[str]:
84- """Basic structural validation of rendered HTML. Returns list of problems."""
85- problems = []
86- if not html or not html.strip():
87- problems.append("Empty HTML body")
88- return problems
89- from bs4 import BeautifulSoup
90- soup = BeautifulSoup(html, "lxml")
91- if soup.find(True) is None:
92- problems.append("No HTML elements found")
93- for tag in soup.find_all(["script", "iframe", "object", "embed", "form"]):
94- problems.append(f"Disallowed tag <{tag.name}> present")
95- for img in soup.find_all("img"):
96- if not (img.get("src") or "").strip():
97- problems.append("img without src")
98- leftovers = re.findall(r"%([A-Za-z0-9_]+)%", html)
99- if leftovers:
100- problems.append(f"Unsubstituted variables remain: {sorted(set(leftovers))[:10]}")
101- return problems
102-
103-
104-def html_to_pdf(html: str) -> bytes:
105- """Real HTML → PDF conversion via WeasyPrint."""
106- try:
107- return WeasyHTML(string=html).write_pdf()
108- except Exception as e:
===CALLERS===
app/tasks/send_tasks.py:196: html_problems = message_builder.validate_html(result_html)
app/api/routers/templates.py:183: problems = message_builder.validate_html(res.text)
app/api/routers/attachments.py:131: problems = validate_html(html_body)
app/api/routers/attachments.py:181: problems = validate_html(tpl.body_html)
app/api/routers/test_send.py:67: problems = message_builder.validate_html(body_html) if body_html else {}