Coverage for src / local_deep_research / exporters / pdf_exporter.py: 100%

35 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-25 01:07 +0000

1"""PDF export service wrapper. 

2 

3This module provides the PDFExporter class that wraps the existing 

4PDFService implementation from web/services/pdf_service.py. 

5 

6The existing PDFService is kept unchanged - this is just a wrapper 

7to integrate it with the modular exporter registry. 

8""" 

9 

10from typing import Optional 

11 

12from loguru import logger 

13 

14from .base import BaseExporter, ExportOptions, ExportResult 

15from .registry import ExporterRegistry 

16 

17# Maximum content size (50 MB) to prevent OOM errors 

18MAX_CONTENT_SIZE = 50 * 1024 * 1024 

19 

20 

21@ExporterRegistry.register 

22class PDFExporter(BaseExporter): 

23 """PDF exporter wrapping the existing PDFService implementation. 

24 

25 This exporter delegates to the existing PDFService which uses 

26 WeasyPrint for PDF generation. No changes are made to the 

27 underlying PDF generation logic. 

28 """ 

29 

30 def __init__(self): 

31 """Initialize PDF exporter.""" 

32 # Import here to avoid circular imports 

33 from ..web.services.pdf_service import get_pdf_service 

34 

35 self._pdf_service = get_pdf_service() 

36 

37 @property 

38 def format_name(self) -> str: 

39 return "pdf" 

40 

41 @property 

42 def file_extension(self) -> str: 

43 return ".pdf" 

44 

45 @property 

46 def mimetype(self) -> str: 

47 return "application/pdf" 

48 

49 def export( 

50 self, 

51 markdown_content: str, 

52 options: Optional[ExportOptions] = None, 

53 ) -> ExportResult: 

54 """Convert markdown content to PDF using existing PDFService. 

55 

56 Args: 

57 markdown_content: The markdown text to convert 

58 options: Optional export options (title, metadata, custom_css) 

59 

60 Returns: 

61 ExportResult with PDF content as bytes, filename, and mimetype 

62 

63 Raises: 

64 ValueError: If content exceeds maximum size limit 

65 """ 

66 try: 

67 # Check content size limit to prevent OOM errors 

68 if len(markdown_content) > MAX_CONTENT_SIZE: 

69 raise ValueError( 

70 f"Content exceeds maximum size of " 

71 f"{MAX_CONTENT_SIZE // (1024 * 1024)} MB" 

72 ) 

73 

74 options = options or ExportOptions() 

75 

76 # Prepend title if needed (for document formats like PDF) 

77 markdown_content = self._prepend_title_if_needed( 

78 markdown_content, options.title 

79 ) 

80 

81 # Extract custom_css if provided 

82 custom_css = None 

83 if options.custom_options: 

84 custom_css = options.custom_options.get("custom_css") 

85 

86 # Delegate to existing PDFService 

87 pdf_bytes = self._pdf_service.markdown_to_pdf( 

88 markdown_content, 

89 title=options.title, 

90 metadata=options.metadata, 

91 custom_css=custom_css, 

92 ) 

93 

94 filename = self._generate_safe_filename(options.title) 

95 

96 logger.info( 

97 f"Generated PDF in memory, size: {len(pdf_bytes)} bytes" 

98 ) 

99 

100 return ExportResult( 

101 content=pdf_bytes, 

102 filename=filename, 

103 mimetype=self.mimetype, 

104 ) 

105 

106 except Exception: 

107 logger.exception("Error generating PDF") 

108 raise