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
« 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
5"""
6Rich table creation utilities for standardized table formatting.
7Provides consistent table patterns across the codebase.
8"""
10from rich.table import Table
11from rich import box
12from typing import List, Dict, Any, Optional
13from dataclasses import dataclass
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
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)
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 )
44 return table
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)
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)
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)
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)
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)
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)
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)
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)
122 return table
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)
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)
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)
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)
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])
178 row_data = [
179 str(index),
180 column,
181 analysis["dtype"],
182 f"{analysis['unique_count']:,}",
183 f"{analysis['null_count']:,}",
184 ]
186 if show_cardinality_pct:
187 cardinality_pct = analysis["cardinality_ratio"] * 100
188 row_data.insert(-1, f"{cardinality_pct:.1f}%")
190 row_data.append(samples)
191 table.add_row(*row_data)
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()
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 )
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])
230 table.add_row(
231 str(index),
232 column,
233 f"{analysis['unique_count']:,}",
234 f"{analysis['null_count']:,}",
235 top_vals
236 )
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"