Coverage for functions \ flipdare \ mailer \ core \ gallery_email_formatter.py: 100%

55 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-05-08 12:22 +1000

1#!/usr/bin/env python 

2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved. 

3# 

4# This file is part of Flipdare's proprietary software and contains 

5# confidential and copyrighted material. Unauthorised copying, 

6# modification, distribution, or use of this file is strictly 

7# prohibited without prior written permission from Flipdare Pty Ltd. 

8# 

9# This software includes third-party components licensed under MIT, 

10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details. 

11# 

12 

13from __future__ import annotations 

14 

15from typing import override 

16import pandas as pd 

17from pandas.io.formats.style_render import CSSDict 

18from flipdare.app_log import LOG 

19from flipdare.app_types import ReportListType 

20from flipdare.mailer.core.email_sanitizer import EmailPostSanitizer 

21from flipdare.mailer.core.table_email_formatter import TableEmailFormatter 

22from flipdare.mailer.email_image import EmailImage 

23from flipdare.constants import IS_TRACE 

24 

25__all__ = ["GalleryEmailFormatter"] 

26 

27 

28class GalleryEmailFormatter(TableEmailFormatter): 

29 def __init__( 

30 self, 

31 headers: list[str], 

32 data: ReportListType, 

33 images: list[EmailImage], 

34 images_per_row: int, 

35 ) -> None: 

36 

37 if IS_TRACE: 

38 msg = ( 

39 f"Initializing EmailReport with {len(headers)} columns " 

40 f"and {len(data)} rows and {len(images)} images." 

41 ) 

42 LOG().trace(msg) 

43 

44 super().__init__(headers=headers, data=data) 

45 self.images = images 

46 self.images_per_row = images_per_row 

47 

48 @override 

49 def tabulate_text(self) -> str: 

50 # tabulate_text does pre-formatting.. 

51 data_text = super().tabulate_text() 

52 

53 all_rows: list[list[str]] = [] 

54 images = self.images 

55 

56 for i in range(len(images)): 

57 # we want 2 columns 

58 # | image <i> | notes | 

59 # | | notes 2 | 

60 notes = images[i].notes 

61 all_rows.append([f"**Image {i+1}**", notes[0]]) 

62 all_rows.extend([["", note] for note in notes[1:]]) 

63 

64 # Create the DataFrame without specific column headers if preferred 

65 df = pd.DataFrame(all_rows) 

66 

67 # Use tablefmt="github" or "pipe" for the cleanest markdown output 

68 image_text = df.to_markdown( 

69 index=False, 

70 headers=["Image", "Notes"], 

71 tablefmt="github", 

72 ) 

73 

74 return EmailPostSanitizer( 

75 data=[image_text, data_text], 

76 headers=["Statistics", "Report Data"], 

77 ).format(is_html=False) 

78 

79 @override 

80 def tabulate_html(self) -> str: 

81 # tabulate_html does pre-formatting.. 

82 data_html = super().tabulate_html() 

83 

84 # 1. Build the row-based data structure 

85 image_data = self._build_image_report_data() 

86 

87 # 2. Create DataFrame (headers can be generic or empty for this layout) 

88 df = pd.DataFrame(image_data) 

89 styler = df.style.hide(axis="index").hide(axis="columns") 

90 

91 # 3. Apply your CSSDict styles 

92 css_style: list[CSSDict] = [ 

93 { 

94 "selector": "td", 

95 "props": [ 

96 ("padding", "10px"), 

97 ("border", "1px solid #ddd"), 

98 ("text-align", "center"), 

99 ], 

100 }, 

101 ] 

102 

103 # 4. Render to HTML (CRITICAL: escape=False to allow <img> tags) 

104 image_html = styler.set_table_styles(css_style).to_html(escape=False) 

105 

106 return EmailPostSanitizer( 

107 data=[image_html, data_html], 

108 headers=["Statistics", "Report Data"], 

109 ).format(is_html=True) 

110 

111 def _build_image_report_data(self) -> list[list[str]]: 

112 data: list[list[str]] = [] 

113 images = self.images 

114 images_per_row = self.images_per_row 

115 

116 for i in range(0, len(images), images_per_row): 

117 chunk = images[i : i + images_per_row] 

118 combined_row = [] 

119 

120 for img in chunk: 

121 # Combine Image and Notes into one HTML block 

122 notes_html = "<br />".join(img.notes) 

123 # We wrap them in a div to control spacing/alignment if needed 

124 cell_content = ( 

125 f'<div style="margin-bottom: 10px;">{img.cid_url}</div>' 

126 f'<div style="font-size: 14px; color: #fff;">{notes_html}</div>' 

127 ) 

128 combined_row.append(cell_content) 

129 

130 # Pad the row with empty strings to maintain table structure 

131 while len(combined_row) < images_per_row: 

132 combined_row.append("") 

133 

134 data.append(combined_row) 

135 

136 return data