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 {}