Coverage for src/utils/table_helpers.py: 49%

76 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-05-25 13:51 -0400

1# Copyright (c) CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL 

2# Unauthorized copying of this file and/or project, via any medium is strictly prohibited. 

3# Direct all questions to legal@cloudzero.com 

4 

5""" 

6Rich table creation utilities for standardized table formatting. 

7Provides consistent table patterns across the codebase. 

8""" 

9 

10from rich.table import Table 

11from rich import box 

12from typing import List, Dict, Any, Optional 

13from dataclasses import dataclass 

14 

15 

16@dataclass 

17class TableColumn: 

18 """Configuration for a table column.""" 

19 name: str 

20 style: str = "default" 

21 justify: str = "left" 

22 width: Optional[int] = None 

23 

24 

25def create_basic_table( 

26 title: Optional[str] = None, 

27 columns: List[TableColumn] = None, 

28 box_style: box.Box = box.ROUNDED, 

29 expand: bool = True 

30) -> Table: 

31 """Create a basic Rich table with standard formatting that uses full screen width.""" 

32 table = Table(title=title, box=box_style, expand=expand, show_lines=True) 

33 

34 if columns: 

35 for col in columns: 

36 table.add_column( 

37 col.name, 

38 style=col.style, 

39 justify=col.justify, 

40 width=col.width, 

41 no_wrap=False # Allow text wrapping for better use of space 

42 ) 

43 

44 return table 

45 

46 

47def create_analysis_table(title: str = "Column Analysis") -> Table: 

48 """Create standardized column analysis table.""" 

49 columns = [ 

50 TableColumn("#", "dim"), 

51 TableColumn("Column", "cyan"), 

52 TableColumn("Type", "yellow"), 

53 TableColumn("Unique Values", "green"), 

54 TableColumn("Null Count", "red"), 

55 TableColumn("Sample Values", "magenta") 

56 ] 

57 return create_basic_table(title, columns) 

58 

59 

60def create_selection_table() -> Table: 

61 """Create standardized column selection table.""" 

62 columns = [ 

63 TableColumn("#", "dim"), 

64 TableColumn("Column", "cyan"), 

65 TableColumn("Unique Values", "green"), 

66 TableColumn("Cardinality Level", "yellow"), 

67 TableColumn("Null Count", "red"), 

68 TableColumn("Cardinality %", "blue"), 

69 TableColumn("Sample Values", "magenta") 

70 ] 

71 return create_basic_table(None, columns) 

72 

73 

74def create_temporal_selection_table() -> Table: 

75 """Create temporal column selection table.""" 

76 columns = [ 

77 TableColumn("#", "dim"), 

78 TableColumn("Column", "cyan"), 

79 TableColumn("Data Type", "yellow"), 

80 TableColumn("Sample Values", "magenta"), 

81 TableColumn("Supported Granularity", "green") 

82 ] 

83 return create_basic_table(None, columns) 

84 

85 

86def create_multi_select_table() -> Table: 

87 """Create multi-selection table for filters/costs.""" 

88 columns = [ 

89 TableColumn("#", "dim"), 

90 TableColumn("Column", "cyan"), 

91 TableColumn("Unique Values", "green"), 

92 TableColumn("Null Count", "red"), 

93 TableColumn("Top Values", "magenta") 

94 ] 

95 return create_basic_table(None, columns) 

96 

97 

98def create_rollup_summary_table() -> Table: 

99 """Create rollup summary table.""" 

100 columns = [ 

101 TableColumn("Metric", "cyan"), 

102 TableColumn("Value", "green") 

103 ] 

104 return create_basic_table("Rollup Summary", columns) 

105 

106 

107def create_rollup_results_table(group_columns: List[str], agg_columns: List[str]) -> Table: 

108 """Create rollup results table with dynamic columns optimized for readability.""" 

109 # Use clean table that auto-sizes to content 

110 table = Table(box=box.ROUNDED, expand=False, show_lines=False) 

111 

112 # Add group columns 

113 for col in group_columns: 

114 # Use reasonable truncation for very long group column names 

115 display_name = col[:20] if len(col) > 20 else col 

116 table.add_column(display_name, style="cyan", no_wrap=True) 

117 

118 # Add aggregation columns (these are now abbreviated and should fit well) 

119 for col in agg_columns: 

120 table.add_column(col, style="green", justify="right", no_wrap=True) 

121 

122 return table 

123 

124 

125def create_cardinality_comparison_table() -> Table: 

126 """Create cardinality comparison table for composite analysis.""" 

127 columns = [ 

128 TableColumn("Column", "cyan"), 

129 TableColumn("Unique Values", "green"), 

130 TableColumn("Cardinality %", "blue"), 

131 TableColumn("Level", "yellow") 

132 ] 

133 return create_basic_table("Cardinality Analysis", columns) 

134 

135 

136def create_value_distribution_table() -> Table: 

137 """Create value distribution table.""" 

138 columns = [ 

139 TableColumn("Value", "cyan"), 

140 TableColumn("Count", "green"), 

141 TableColumn("Percentage", "blue") 

142 ] 

143 return create_basic_table("Value Distribution", columns) 

144 

145 

146def create_temporal_summary_table() -> Table: 

147 """Create temporal selection summary table.""" 

148 columns = [ 

149 TableColumn("Setting", "cyan"), 

150 TableColumn("Value", "green") 

151 ] 

152 return create_basic_table("Temporal Selection Summary", columns) 

153 

154 

155def create_data_quality_table() -> Table: 

156 """Create data quality overview table.""" 

157 columns = [ 

158 TableColumn("Column", "cyan"), 

159 TableColumn("Data Type", "yellow"), 

160 TableColumn("Non-Null Count", "green"), 

161 TableColumn("Null Count", "red"), 

162 TableColumn("Unique Count", "blue"), 

163 TableColumn("Sample Values", "magenta") 

164 ] 

165 return create_basic_table("Data Quality Overview", columns) 

166 

167 

168def add_analysis_row( 

169 table: Table, 

170 index: int, 

171 column: str, 

172 analysis: Dict[str, Any], 

173 show_cardinality_pct: bool = False 

174) -> None: 

175 """Add a standardized analysis row to a table.""" 

176 samples = ", ".join(str(v) for v in analysis["sample_values"][:5]) 

177 

178 row_data = [ 

179 str(index), 

180 column, 

181 analysis["dtype"], 

182 f"{analysis['unique_count']:,}", 

183 f"{analysis['null_count']:,}", 

184 ] 

185 

186 if show_cardinality_pct: 

187 cardinality_pct = analysis["cardinality_ratio"] * 100 

188 row_data.insert(-1, f"{cardinality_pct:.1f}%") 

189 

190 row_data.append(samples) 

191 table.add_row(*row_data) 

192 

193 

194def add_selection_row( 

195 table: Table, 

196 index: int, 

197 column: str, 

198 analysis: Dict[str, Any] 

199) -> None: 

200 """Add a standardized selection row to a table.""" 

201 samples = ", ".join(str(v) for v in analysis["sample_values"][:5]) 

202 cardinality_pct = analysis["cardinality_ratio"] * 100 

203 cardinality_level = analysis["cardinality"].replace("_", " ").title() 

204 

205 table.add_row( 

206 str(index), 

207 column, 

208 f"{analysis['unique_count']:,}", 

209 cardinality_level, 

210 f"{analysis['null_count']:,}", 

211 f"{cardinality_pct:.1f}%", 

212 samples 

213 ) 

214 

215 

216def add_multi_select_row( 

217 table: Table, 

218 index: int, 

219 column: str, 

220 analysis: Dict[str, Any] 

221) -> None: 

222 """Add a standardized multi-select row to a table.""" 

223 if analysis["value_counts"]: 

224 top_vals = ", ".join( 

225 f"{k}({v})" for k, v in list(analysis["value_counts"].items())[:5] 

226 ) 

227 else: 

228 top_vals = ", ".join(str(v) for v in analysis["sample_values"][:5]) 

229 

230 table.add_row( 

231 str(index), 

232 column, 

233 f"{analysis['unique_count']:,}", 

234 f"{analysis['null_count']:,}", 

235 top_vals 

236 ) 

237 

238 

239def format_file_size(size_bytes: int) -> str: 

240 """Format file size in human-readable format.""" 

241 size_mb = size_bytes / (1024 * 1024) 

242 if size_mb < 1: 

243 return f"{size_bytes / 1024:.1f}KB" 

244 elif size_mb < 1024: 

245 return f"{size_mb:.1f}MB" 

246 else: 

247 return f"{size_mb / 1024:.1f}GB"