Stub WeasyPrint for CI-friendly PDF generation and verify HTML render, PDF path, and HTML file output.
98 lines
3.4 KiB
Python
98 lines
3.4 KiB
Python
"""Report engine — renders Jinja2 HTML templates to PDF, DOCX, and HTML.
|
|
|
|
Uses WeasyPrint for PDF generation and docxtpl for DOCX.
|
|
"""
|
|
|
|
import os
|
|
import uuid
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
from app.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ReportEngine:
|
|
"""Template-based report generator supporting PDF, DOCX, and HTML output."""
|
|
|
|
def __init__(self) -> None:
|
|
self.jinja_env = Environment(
|
|
loader=FileSystemLoader(settings.REPORT_TEMPLATES_DIR),
|
|
autoescape=True,
|
|
)
|
|
os.makedirs(settings.REPORT_OUTPUT_DIR, exist_ok=True)
|
|
|
|
def render_html(self, template_name: str, context: dict) -> str:
|
|
"""Render a Jinja2 template to an HTML string."""
|
|
template = self.jinja_env.get_template(f"{template_name}.html")
|
|
context.setdefault("company_name", settings.COMPANY_NAME)
|
|
context.setdefault("generated_at", datetime.utcnow().strftime("%B %d, %Y %H:%M UTC"))
|
|
return template.render(context)
|
|
|
|
def generate_pdf(self, template_name: str, context: dict) -> str:
|
|
"""Render HTML and convert to PDF with WeasyPrint."""
|
|
from weasyprint import HTML, CSS
|
|
|
|
html_content = self.render_html(template_name, context)
|
|
css_path = os.path.join(settings.REPORT_TEMPLATES_DIR, "styles", "report.css")
|
|
output_path = os.path.join(
|
|
settings.REPORT_OUTPUT_DIR,
|
|
f"{template_name}_{uuid.uuid4().hex[:8]}.pdf",
|
|
)
|
|
|
|
stylesheets = []
|
|
if os.path.exists(css_path):
|
|
stylesheets.append(CSS(filename=css_path))
|
|
|
|
HTML(
|
|
string=html_content,
|
|
base_url=settings.REPORT_TEMPLATES_DIR,
|
|
).write_pdf(output_path, stylesheets=stylesheets)
|
|
|
|
logger.info("PDF generated: %s", output_path)
|
|
return output_path
|
|
|
|
def generate_docx(self, template_name: str, context: dict) -> str:
|
|
"""Render a .docx template with docxtpl."""
|
|
from docxtpl import DocxTemplate
|
|
|
|
template_path = os.path.join(
|
|
settings.REPORT_TEMPLATES_DIR, f"{template_name}.docx"
|
|
)
|
|
output_path = os.path.join(
|
|
settings.REPORT_OUTPUT_DIR,
|
|
f"{template_name}_{uuid.uuid4().hex[:8]}.docx",
|
|
)
|
|
|
|
doc = DocxTemplate(template_path)
|
|
context.setdefault("company_name", settings.COMPANY_NAME)
|
|
context.setdefault("generated_at", datetime.utcnow().strftime("%B %d, %Y"))
|
|
doc.render(context)
|
|
doc.save(output_path)
|
|
|
|
logger.info("DOCX generated: %s", output_path)
|
|
return output_path
|
|
|
|
def generate_html(self, template_name: str, context: dict) -> str:
|
|
"""Render and save a standalone HTML report (alias for spec compatibility)."""
|
|
return self.generate_html_file(template_name, context)
|
|
|
|
def generate_html_file(self, template_name: str, context: dict) -> str:
|
|
"""Render and save a standalone HTML report."""
|
|
html_content = self.render_html(template_name, context)
|
|
output_path = os.path.join(
|
|
settings.REPORT_OUTPUT_DIR,
|
|
f"{template_name}_{uuid.uuid4().hex[:8]}.html",
|
|
)
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
f.write(html_content)
|
|
|
|
logger.info("HTML report generated: %s", output_path)
|
|
return output_path
|
|
|
|
|
|
report_engine = ReportEngine()
|