Coverage for src/local_deep_research/exporters/pdf_exporter.py: 100%
33 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-03 23:15 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-03 23:15 +0000
1"""PDF export service wrapper.
3This module provides the PDFExporter class that wraps the existing
4PDFService implementation from web/services/pdf_service.py.
6The existing PDFService is kept unchanged - this is just a wrapper
7to integrate it with the modular exporter registry.
8"""
10from typing import Optional
12from loguru import logger
14from ..web.services.pdf_service import get_pdf_service
15from .base import BaseExporter, ExportOptions, ExportResult
16from .registry import ExporterRegistry
19@ExporterRegistry.register
20class PDFExporter(BaseExporter):
21 """PDF exporter wrapping the existing PDFService implementation.
23 This exporter delegates to the existing PDFService which uses
24 WeasyPrint for PDF generation. No changes are made to the
25 underlying PDF generation logic.
26 """
28 def __init__(self):
29 """Initialize PDF exporter.
31 Note: pdf_service import is deferred to export() so that
32 registration succeeds even when WeasyPrint system libraries
33 (Pango, Cairo, GLib) are not installed.
34 """
36 @property
37 def format_name(self) -> str:
38 return "pdf"
40 @property
41 def file_extension(self) -> str:
42 return ".pdf"
44 @property
45 def mimetype(self) -> str:
46 return "application/pdf"
48 def export(
49 self,
50 markdown_content: str,
51 options: Optional[ExportOptions] = None,
52 ) -> ExportResult:
53 """Convert markdown content to PDF using existing PDFService.
55 Args:
56 markdown_content: The markdown text to convert
57 options: Optional export options (title, metadata, custom_css)
59 Returns:
60 ExportResult with PDF content as bytes, filename, and mimetype
62 Raises:
63 ValueError: If content exceeds maximum size limit
64 """
65 try:
66 # Check content size limit to prevent OOM errors
67 self._validate_content_size(markdown_content)
69 pdf_service = get_pdf_service()
71 options = options or ExportOptions()
73 # Prepend title if needed (for document formats like PDF)
74 markdown_content = self._prepend_title_if_needed(
75 markdown_content, options.title
76 )
78 # Extract custom_css if provided
79 custom_css = None
80 if options.custom_options:
81 custom_css = options.custom_options.get("custom_css")
83 # Delegate to existing PDFService
84 pdf_bytes = pdf_service.markdown_to_pdf(
85 markdown_content,
86 title=options.title,
87 metadata=options.metadata,
88 custom_css=custom_css,
89 )
91 filename = self._generate_safe_filename(options.title)
93 logger.info(
94 f"Generated PDF in memory, size: {len(pdf_bytes)} bytes"
95 )
97 return ExportResult(
98 content=pdf_bytes,
99 filename=filename,
100 mimetype=self.mimetype,
101 )
103 except Exception:
104 logger.exception("Error generating PDF")
105 raise