parqv 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- parqv/__init__.py +31 -0
- parqv/app.py +84 -102
- parqv/cli.py +112 -0
- parqv/core/__init__.py +31 -0
- parqv/core/config.py +26 -0
- parqv/core/file_utils.py +88 -0
- parqv/core/handler_factory.py +90 -0
- parqv/core/logging.py +46 -0
- parqv/data_sources/__init__.py +48 -0
- parqv/data_sources/base/__init__.py +28 -0
- parqv/data_sources/base/exceptions.py +38 -0
- parqv/{handlers/base_handler.py → data_sources/base/handler.py} +54 -25
- parqv/{handlers → data_sources/formats}/__init__.py +13 -5
- parqv/data_sources/formats/csv.py +460 -0
- parqv/{handlers → data_sources/formats}/json.py +68 -32
- parqv/{handlers → data_sources/formats}/parquet.py +67 -56
- parqv/views/__init__.py +38 -0
- parqv/views/base.py +98 -0
- parqv/views/components/__init__.py +13 -0
- parqv/views/components/enhanced_data_table.py +152 -0
- parqv/views/components/error_display.py +72 -0
- parqv/views/components/loading_display.py +44 -0
- parqv/views/data_view.py +119 -46
- parqv/views/metadata_view.py +57 -20
- parqv/views/schema_view.py +190 -200
- parqv/views/utils/__init__.py +19 -0
- parqv/views/utils/data_formatters.py +184 -0
- parqv/views/utils/stats_formatters.py +220 -0
- parqv/views/utils/visualization.py +204 -0
- {parqv-0.2.0.dist-info → parqv-0.3.0.dist-info}/METADATA +5 -6
- parqv-0.3.0.dist-info/RECORD +36 -0
- {parqv-0.2.0.dist-info → parqv-0.3.0.dist-info}/WHEEL +1 -1
- parqv-0.2.0.dist-info/RECORD +0 -17
- {parqv-0.2.0.dist-info → parqv-0.3.0.dist-info}/entry_points.txt +0 -0
- {parqv-0.2.0.dist-info → parqv-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {parqv-0.2.0.dist-info → parqv-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,220 @@
|
|
1
|
+
"""
|
2
|
+
Statistics formatting utilities for parqv views.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Any, Dict, List, Union
|
6
|
+
|
7
|
+
from rich.text import Text
|
8
|
+
|
9
|
+
from .visualization import create_text_histogram, should_show_histogram
|
10
|
+
|
11
|
+
|
12
|
+
def format_stats_for_display(stats_data: Dict[str, Any]) -> List[Union[str, Text]]:
|
13
|
+
"""
|
14
|
+
Format statistics dictionary for display as lines of rich text.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
stats_data: Raw statistics dictionary from handler
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
List of formatted lines ready for display
|
21
|
+
"""
|
22
|
+
if not stats_data:
|
23
|
+
return [Text.from_markup("[red]No statistics data available.[/red]")]
|
24
|
+
|
25
|
+
lines: List[Union[str, Text]] = []
|
26
|
+
|
27
|
+
# Extract basic column information
|
28
|
+
col_name = stats_data.get("column", "N/A")
|
29
|
+
col_type = stats_data.get("type", "Unknown")
|
30
|
+
nullable_val = stats_data.get("nullable")
|
31
|
+
|
32
|
+
# Format column header
|
33
|
+
lines.extend(_format_column_header(col_name, col_type, nullable_val))
|
34
|
+
|
35
|
+
# Handle calculation errors
|
36
|
+
calc_error = stats_data.get("error")
|
37
|
+
if calc_error:
|
38
|
+
lines.extend(_format_error_section(calc_error))
|
39
|
+
|
40
|
+
# Add informational messages
|
41
|
+
message = stats_data.get("message")
|
42
|
+
if message:
|
43
|
+
lines.extend(_format_message_section(message))
|
44
|
+
|
45
|
+
# Format calculated statistics
|
46
|
+
calculated = stats_data.get("calculated")
|
47
|
+
if calculated:
|
48
|
+
lines.extend(_format_calculated_stats(calculated, has_error=bool(calc_error)))
|
49
|
+
|
50
|
+
return lines
|
51
|
+
|
52
|
+
|
53
|
+
def format_column_info(column_name: str, column_type: str, nullable: Any) -> List[Union[str, Text]]:
|
54
|
+
"""
|
55
|
+
Format basic column information for display.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
column_name: Name of the column
|
59
|
+
column_type: Type of the column
|
60
|
+
nullable: Nullability information
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
List of formatted lines for column info
|
64
|
+
"""
|
65
|
+
return _format_column_header(column_name, column_type, nullable)
|
66
|
+
|
67
|
+
|
68
|
+
def _format_column_header(col_name: str, col_type: str, nullable_val: Any) -> List[Union[str, Text]]:
|
69
|
+
"""Format the column header section."""
|
70
|
+
# Determine nullability display
|
71
|
+
if nullable_val is True:
|
72
|
+
nullable_str = "Nullable"
|
73
|
+
elif nullable_val is False:
|
74
|
+
nullable_str = "Required"
|
75
|
+
else:
|
76
|
+
nullable_str = "Unknown Nullability"
|
77
|
+
|
78
|
+
lines = [
|
79
|
+
Text.assemble(("Column: ", "bold"), f"`{col_name}`"),
|
80
|
+
Text.assemble(("Type: ", "bold"), f"{col_type} ({nullable_str})"),
|
81
|
+
"─" * (len(col_name) + len(col_type) + 20)
|
82
|
+
]
|
83
|
+
|
84
|
+
return lines
|
85
|
+
|
86
|
+
|
87
|
+
def _format_error_section(calc_error: str) -> List[Union[str, Text]]:
|
88
|
+
"""Format the error section."""
|
89
|
+
return [
|
90
|
+
Text("Calculation Error:", style="bold red"),
|
91
|
+
f"```\n{calc_error}\n```",
|
92
|
+
""
|
93
|
+
]
|
94
|
+
|
95
|
+
|
96
|
+
def _format_message_section(message: str) -> List[Union[str, Text]]:
|
97
|
+
"""Format the informational message section."""
|
98
|
+
return [
|
99
|
+
Text(f"Info: {message}", style="italic cyan"),
|
100
|
+
""
|
101
|
+
]
|
102
|
+
|
103
|
+
|
104
|
+
def _format_calculated_stats(calculated: Dict[str, Any], has_error: bool = False) -> List[Union[str, Text]]:
|
105
|
+
"""Format the calculated statistics section."""
|
106
|
+
lines = [Text("Calculated Statistics:", style="bold")]
|
107
|
+
|
108
|
+
# Define the order of statistics to display
|
109
|
+
stats_order = [
|
110
|
+
"Total Count", "Valid Count", "Null Count", "Null Percentage",
|
111
|
+
"Distinct Count", "Distinct Values (Approx)",
|
112
|
+
"Min", "Max", "Mean", "Median (50%)", "StdDev", "Variance",
|
113
|
+
"True Count", "False Count",
|
114
|
+
"Value Counts"
|
115
|
+
]
|
116
|
+
|
117
|
+
found_stats = False
|
118
|
+
|
119
|
+
for key in stats_order:
|
120
|
+
if key in calculated:
|
121
|
+
found_stats = True
|
122
|
+
value = calculated[key]
|
123
|
+
lines.extend(_format_single_stat(key, value))
|
124
|
+
|
125
|
+
# Add any additional stats not in the predefined order (excluding internal histogram data)
|
126
|
+
for key, value in calculated.items():
|
127
|
+
if key not in stats_order and not key.startswith('_'): # Skip internal fields
|
128
|
+
found_stats = True
|
129
|
+
lines.extend(_format_single_stat(key, value))
|
130
|
+
|
131
|
+
# Handle case where no stats were found
|
132
|
+
if not found_stats and not has_error:
|
133
|
+
lines.append(Text(" (No specific stats calculated for this type)", style="dim"))
|
134
|
+
|
135
|
+
# Add histogram visualization for numeric data
|
136
|
+
if "_histogram_data" in calculated and "_data_type" in calculated:
|
137
|
+
if calculated["_data_type"] == "numeric":
|
138
|
+
lines.extend(_format_histogram_visualization(calculated))
|
139
|
+
|
140
|
+
return lines
|
141
|
+
|
142
|
+
|
143
|
+
def _format_single_stat(key: str, value: Any) -> List[Union[str, Text]]:
|
144
|
+
"""Format a single statistic entry."""
|
145
|
+
lines = []
|
146
|
+
|
147
|
+
if key == "Value Counts" and isinstance(value, dict):
|
148
|
+
lines.append(f" - {key}:")
|
149
|
+
for sub_key, sub_val in value.items():
|
150
|
+
sub_val_str = _format_stat_value(sub_val)
|
151
|
+
lines.append(f" - {sub_key}: {sub_val_str}")
|
152
|
+
else:
|
153
|
+
formatted_value = _format_stat_value(value)
|
154
|
+
lines.append(f" - {key}: {formatted_value}")
|
155
|
+
|
156
|
+
return lines
|
157
|
+
|
158
|
+
|
159
|
+
def _format_stat_value(value: Any) -> str:
|
160
|
+
"""Format a single statistic value."""
|
161
|
+
if isinstance(value, (int, float)):
|
162
|
+
if isinstance(value, int):
|
163
|
+
return f"{value:,}"
|
164
|
+
else:
|
165
|
+
return f"{value:,.4f}"
|
166
|
+
else:
|
167
|
+
return str(value)
|
168
|
+
|
169
|
+
|
170
|
+
def _format_histogram_visualization(calculated: Dict[str, Any]) -> List[Union[str, Text]]:
|
171
|
+
"""Format histogram visualization for numeric data."""
|
172
|
+
lines = []
|
173
|
+
|
174
|
+
try:
|
175
|
+
histogram_data = calculated.get("_histogram_data", [])
|
176
|
+
if not histogram_data:
|
177
|
+
return lines
|
178
|
+
|
179
|
+
# Check if we should show histogram
|
180
|
+
distinct_count_str = calculated.get("Distinct Count", "0")
|
181
|
+
try:
|
182
|
+
# Remove commas and convert to int
|
183
|
+
distinct_count = int(distinct_count_str.replace(",", ""))
|
184
|
+
except (ValueError, AttributeError):
|
185
|
+
distinct_count = len(set(histogram_data))
|
186
|
+
|
187
|
+
total_count = len(histogram_data)
|
188
|
+
|
189
|
+
if should_show_histogram("numeric", distinct_count, total_count):
|
190
|
+
lines.append("")
|
191
|
+
lines.append(Text("Data Distribution:", style="bold cyan"))
|
192
|
+
|
193
|
+
# Create histogram
|
194
|
+
histogram_lines = create_text_histogram(
|
195
|
+
data=histogram_data,
|
196
|
+
bins=15,
|
197
|
+
width=50,
|
198
|
+
height=8,
|
199
|
+
title=None
|
200
|
+
)
|
201
|
+
|
202
|
+
# Add each histogram line
|
203
|
+
for line in histogram_lines:
|
204
|
+
if isinstance(line, str):
|
205
|
+
lines.append(f" {line}")
|
206
|
+
else:
|
207
|
+
lines.append(line)
|
208
|
+
else:
|
209
|
+
# For discrete data, show a note
|
210
|
+
if distinct_count < total_count * 0.1: # Less than 10% unique values
|
211
|
+
lines.append("")
|
212
|
+
lines.append(Text("Note: Data appears to be discrete/categorical", style="dim italic"))
|
213
|
+
lines.append(Text("(Histogram not shown for discrete values)", style="dim italic"))
|
214
|
+
|
215
|
+
except Exception as e:
|
216
|
+
# Don't fail the whole stats display if histogram fails
|
217
|
+
lines.append("")
|
218
|
+
lines.append(Text(f"Note: Could not generate histogram: {e}", style="dim red"))
|
219
|
+
|
220
|
+
return lines
|
@@ -0,0 +1,204 @@
|
|
1
|
+
"""
|
2
|
+
Visualization utilities for parqv views.
|
3
|
+
|
4
|
+
Provides text-based data visualization functions like ASCII histograms.
|
5
|
+
"""
|
6
|
+
import math
|
7
|
+
from typing import List, Union, Optional
|
8
|
+
|
9
|
+
TICK_CHARS = [' ', '▂', '▃', '▄', '▅', '▆', '▇', '█']
|
10
|
+
|
11
|
+
|
12
|
+
def create_text_histogram(
|
13
|
+
data: List[Union[int, float]],
|
14
|
+
bins: int = 15,
|
15
|
+
width: int = 60,
|
16
|
+
height: int = 8,
|
17
|
+
title: Optional[str] = None
|
18
|
+
) -> List[str]:
|
19
|
+
"""
|
20
|
+
Create a professional, text-based histogram from numerical data.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
data: List of numerical values.
|
24
|
+
bins: The number of bins for the histogram.
|
25
|
+
width: The total character width of the output histogram.
|
26
|
+
height: The maximum height of the histogram bars in lines.
|
27
|
+
title: An optional title for the histogram.
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
A list of strings representing the histogram, ready for printing.
|
31
|
+
"""
|
32
|
+
if not data:
|
33
|
+
return ["(No data available for histogram)"]
|
34
|
+
|
35
|
+
# 1. Sanitize the input data
|
36
|
+
clean_data = [float(val) for val in data if isinstance(val, (int, float)) and math.isfinite(val)]
|
37
|
+
|
38
|
+
if not clean_data:
|
39
|
+
return ["(No valid numerical data to plot)"]
|
40
|
+
|
41
|
+
min_val, max_val = min(clean_data), max(clean_data)
|
42
|
+
|
43
|
+
if min_val == max_val:
|
44
|
+
return [f"(All values are identical: {_format_number(min_val)})"]
|
45
|
+
|
46
|
+
# 2. Create bins and count frequencies
|
47
|
+
# Add a small epsilon to the range to ensure max_val falls into the last bin
|
48
|
+
epsilon = (max_val - min_val) / 1e9
|
49
|
+
value_range = (max_val - min_val) + epsilon
|
50
|
+
bin_width = value_range / bins
|
51
|
+
|
52
|
+
bin_counts = [0] * bins
|
53
|
+
for value in clean_data:
|
54
|
+
bin_index = int((value - min_val) / bin_width)
|
55
|
+
bin_counts[bin_index] += 1
|
56
|
+
|
57
|
+
# 3. Render the histogram
|
58
|
+
return _render_histogram(
|
59
|
+
bin_counts=bin_counts,
|
60
|
+
min_val=min_val,
|
61
|
+
max_val=max_val,
|
62
|
+
width=width,
|
63
|
+
height=height,
|
64
|
+
title=title
|
65
|
+
)
|
66
|
+
|
67
|
+
|
68
|
+
def _render_histogram(
|
69
|
+
bin_counts: List[int],
|
70
|
+
min_val: float,
|
71
|
+
max_val: float,
|
72
|
+
width: int,
|
73
|
+
height: int,
|
74
|
+
title: Optional[str]
|
75
|
+
) -> List[str]:
|
76
|
+
"""
|
77
|
+
Internal function to render the histogram components into ASCII art.
|
78
|
+
"""
|
79
|
+
lines = []
|
80
|
+
if title:
|
81
|
+
lines.append(title.center(width))
|
82
|
+
|
83
|
+
max_count = max(bin_counts) if bin_counts else 0
|
84
|
+
if max_count == 0:
|
85
|
+
return lines + ["(No data falls within histogram bins)"]
|
86
|
+
|
87
|
+
# --- Layout Calculations ---
|
88
|
+
y_axis_width = len(str(max_count))
|
89
|
+
plot_width = width - y_axis_width - 3 # Reserve space for "| " and axis
|
90
|
+
if plot_width <= 0:
|
91
|
+
return ["(Terminal width too narrow to draw histogram)"]
|
92
|
+
|
93
|
+
# Resample the data bins to fit the available plot_width.
|
94
|
+
# This stretches or shrinks the histogram to match the screen space.
|
95
|
+
display_bins = []
|
96
|
+
num_data_bins = len(bin_counts)
|
97
|
+
for i in range(plot_width):
|
98
|
+
# Find the corresponding data bin for this screen column
|
99
|
+
data_bin_index = int(i * num_data_bins / plot_width)
|
100
|
+
display_bins.append(bin_counts[data_bin_index])
|
101
|
+
|
102
|
+
# --- Y-Axis and Bars (Top to Bottom) ---
|
103
|
+
for row in range(height, -1, -1):
|
104
|
+
line = ""
|
105
|
+
# Y-axis labels
|
106
|
+
if row == height:
|
107
|
+
line += f"{max_count:<{y_axis_width}} | "
|
108
|
+
elif row == 0:
|
109
|
+
line += f"{0:<{y_axis_width}} +-"
|
110
|
+
else:
|
111
|
+
line += " " * y_axis_width + " | "
|
112
|
+
|
113
|
+
# Bars - now iterate over the resampled display_bins
|
114
|
+
for count in display_bins:
|
115
|
+
# Scale current count to the available height
|
116
|
+
scaled_height = (count / max_count) * height
|
117
|
+
|
118
|
+
# Determine character based on height relative to current row
|
119
|
+
if scaled_height >= row:
|
120
|
+
line += TICK_CHARS[-1] # Full block for the solid part of the bar
|
121
|
+
elif scaled_height > row - 1:
|
122
|
+
# This is the top of the bar, use a partial character
|
123
|
+
partial_index = int((scaled_height - row + 1) * (len(TICK_CHARS) - 1))
|
124
|
+
line += TICK_CHARS[max(0, partial_index)]
|
125
|
+
elif row == 0:
|
126
|
+
line += "-" # X-axis line
|
127
|
+
else:
|
128
|
+
line += " " # Empty space above the bar
|
129
|
+
|
130
|
+
lines.append(line)
|
131
|
+
|
132
|
+
# --- X-Axis Labels ---
|
133
|
+
x_axis_labels = _create_x_axis_labels(min_val, max_val, plot_width)
|
134
|
+
label_line = " " * (y_axis_width + 3) + x_axis_labels
|
135
|
+
lines.append(label_line)
|
136
|
+
|
137
|
+
return lines
|
138
|
+
|
139
|
+
|
140
|
+
def _create_x_axis_labels(min_val: float, max_val: float, plot_width: int) -> str:
|
141
|
+
"""Create a formatted string for the X-axis labels."""
|
142
|
+
min_label = _format_number(min_val)
|
143
|
+
max_label = _format_number(max_val)
|
144
|
+
|
145
|
+
available_width = plot_width - len(min_label) - len(max_label)
|
146
|
+
|
147
|
+
if available_width < 4:
|
148
|
+
return f"{min_label}{' ' * (plot_width - len(min_label) - len(max_label))}{max_label}"
|
149
|
+
|
150
|
+
mid_val = (min_val + max_val) / 2
|
151
|
+
mid_label = _format_number(mid_val)
|
152
|
+
|
153
|
+
spacing1 = (plot_width // 2) - len(min_label) - (len(mid_label) // 2)
|
154
|
+
spacing2 = (plot_width - (plot_width // 2)) - (len(mid_label) - (len(mid_label) // 2)) - len(max_label)
|
155
|
+
|
156
|
+
if spacing1 < 1 or spacing2 < 1:
|
157
|
+
return f"{min_label}{' ' * (plot_width - len(min_label) - len(max_label))}{max_label}"
|
158
|
+
|
159
|
+
return f"{min_label}{' ' * spacing1}{mid_label}{' ' * spacing2}{max_label}"
|
160
|
+
|
161
|
+
|
162
|
+
def _format_number(value: float) -> str:
|
163
|
+
"""Format a number nicely for display on an axis."""
|
164
|
+
if abs(value) < 1e-4 and value != 0:
|
165
|
+
return f"{value:.1e}"
|
166
|
+
if abs(value) >= 1e5:
|
167
|
+
return f"{value:.1e}"
|
168
|
+
if math.isclose(value, int(value)):
|
169
|
+
return str(int(value))
|
170
|
+
if abs(value) < 10:
|
171
|
+
return f"{value:.2f}"
|
172
|
+
if abs(value) < 100:
|
173
|
+
return f"{value:.1f}"
|
174
|
+
return str(int(value))
|
175
|
+
|
176
|
+
|
177
|
+
def should_show_histogram(data_type: str, distinct_count: int, total_count: int) -> bool:
|
178
|
+
"""
|
179
|
+
Determine if a histogram should be shown for this data.
|
180
|
+
This function uses a set of heuristics to decide if the data is
|
181
|
+
continuous enough to warrant a histogram visualization.
|
182
|
+
"""
|
183
|
+
# 1. Type Check: Histograms are only meaningful for numeric data.
|
184
|
+
if 'numeric' not in data_type and 'integer' not in data_type and 'float' not in data_type:
|
185
|
+
return False
|
186
|
+
|
187
|
+
# 2. Data Volume Check: Don't render if there's too little data or no variation.
|
188
|
+
if total_count < 20 or distinct_count <= 1:
|
189
|
+
return False
|
190
|
+
|
191
|
+
# 3. Categorical Data Filter: If the number of distinct values is very low,
|
192
|
+
# treat it as categorical data (e.g., ratings from 1-10, months 1-12).
|
193
|
+
if distinct_count < 15:
|
194
|
+
return False
|
195
|
+
|
196
|
+
# 4. High Cardinality Filter: If almost every value is unique (like an ID or index),
|
197
|
+
# a histogram is not useful as most bars would have a height of 1.
|
198
|
+
distinct_ratio = distinct_count / total_count
|
199
|
+
if distinct_ratio > 0.95:
|
200
|
+
return False
|
201
|
+
|
202
|
+
# 5. Pass: If the data passes all the above filters, it is considered
|
203
|
+
# sufficiently continuous to be visualized with a histogram.
|
204
|
+
return True
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: parqv
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0
|
4
4
|
Summary: An interactive Python TUI for visualizing, exploring, and analyzing files directly in your terminal.
|
5
5
|
Author-email: Sangmin Yoon <sanspareilsmyn@gmail.com>
|
6
6
|
License-Expression: Apache-2.0
|
@@ -23,14 +23,13 @@ Dynamic: license-file
|
|
23
23
|
|
24
24
|
---
|
25
25
|
|
26
|
-
**Supported File Formats:** ✅ **Parquet** | ✅ **JSON** / **JSON Lines (ndjson)** | *(More planned!)*
|
26
|
+
**Supported File Formats:** ✅ **Parquet** | ✅ **JSON** / **JSON Lines (ndjson)** | ✅ **CSV / TSV** | *(More planned!)*
|
27
27
|
|
28
28
|
---
|
29
29
|
|
30
|
-
**`parqv` is a Python-based interactive TUI (Text User Interface) tool designed to explore, analyze, and understand various data file formats directly within your terminal.**
|
31
|
-
|
32
|
-
## 💻 Demo (Showing Parquet)
|
30
|
+
**`parqv` is a Python-based interactive TUI (Text User Interface) tool designed to explore, analyze, and understand various data file formats directly within your terminal.** `parqv` aims to provide a unified, visual experience for quick data inspection without leaving your console.
|
33
31
|
|
32
|
+
## 💻 Demo
|
34
33
|

|
35
34
|
*(Demo shows Parquet features; UI adapts for other formats)*
|
36
35
|
|
@@ -47,7 +46,7 @@ Dynamic: license-file
|
|
47
46
|
* **🔌 Extensible:** Designed with a handler interface to easily add support for more file formats in the future (like CSV, Arrow IPC, etc.).
|
48
47
|
|
49
48
|
## ✨ Features (TUI Mode)
|
50
|
-
* **Multi-Format Support:**
|
49
|
+
* **Multi-Format Support:** Now supports **Parquet** (`.parquet`), **JSON/JSON Lines** (`.json`, `.ndjson`), and **CSV/TSV** (`.csv`, `.tsv`). Run `parqv <your_file.{parquet,json,ndjson,csv,tsv}>`.
|
51
50
|
* **Metadata Panel:** Displays key file information (path, format, size, total rows, column count, etc.). *Fields may vary slightly depending on the file format.*
|
52
51
|
* **Schema Explorer:**
|
53
52
|
* Interactive list view of columns.
|
@@ -0,0 +1,36 @@
|
|
1
|
+
parqv/__init__.py,sha256=CcARikIb8knQqd3bGu6Y9exgSbzdywjdORz15VKKxmU,611
|
2
|
+
parqv/app.py,sha256=KXfL-RfMOOngzxn0uEeWa5UQgHRISBBl1IHK0ffQlzI,5556
|
3
|
+
parqv/cli.py,sha256=9KPOYywA53vUVp_5RI2lFgKtZay2EHosOsTo-sz4rOU,3242
|
4
|
+
parqv/parqv.css,sha256=C42ZXUwMX1ZXfGo0AmixbHxz0CWKzWBHZ_hkhq5aehg,2920
|
5
|
+
parqv/core/__init__.py,sha256=C8P-wqP72hk54qcjRNjDQ9X4hTu9eRttAyVynCAeDQw,791
|
6
|
+
parqv/core/config.py,sha256=HgKUcfs3gEDyRrNS79jiIhI_DOfXCUK7sN77rVuNOlA,561
|
7
|
+
parqv/core/file_utils.py,sha256=m7d8wD9nxXCmfUU1b6IGBqjW0mBL2Nr40r9M8NI67FY,2374
|
8
|
+
parqv/core/handler_factory.py,sha256=foVrcvcV2gWuatD_d11PUcGIKjnKLj-FMBrZff1qm3I,3074
|
9
|
+
parqv/core/logging.py,sha256=fom0zPykBgn7zpW_vlvaCDlCERI1lccdjtBRMES94-A,1173
|
10
|
+
parqv/data_sources/__init__.py,sha256=EGjRMSPeMzLPcdMdZ3axZzUfagg6PTWhyNLwLoQYuqE,951
|
11
|
+
parqv/data_sources/base/__init__.py,sha256=Alo4tGZJdUsfvGvDgpRodur3xn-SO7hgeODmhx6tA6U,524
|
12
|
+
parqv/data_sources/base/exceptions.py,sha256=QjTQqPw_LgEYchN2OjWCOkUuRV-Sn9I1J3gmoiS4ekA,802
|
13
|
+
parqv/data_sources/base/handler.py,sha256=I0Hmsf-WYhWyLGUHEV902iPS7Gg4dDkRPgyXOfGZSTQ,4429
|
14
|
+
parqv/data_sources/formats/__init__.py,sha256=825j-DwYpc-9nSKKhaAo-G72DK-L2AyjB6_HCLVDQpI,416
|
15
|
+
parqv/data_sources/formats/csv.py,sha256=SFIyQII4DEH8_2SlHLhR9cwzIySkopu48N85msNHC-E,18387
|
16
|
+
parqv/data_sources/formats/json.py,sha256=9pVnKbtK1MEuZCT1UbUbWnFJSi0GnH6Bnf0yhqgH8es,21570
|
17
|
+
parqv/data_sources/formats/parquet.py,sha256=J-oaxCojTz3590asPV59V_SK0LcIiYRrKxnVAm7WshQ,32821
|
18
|
+
parqv/views/__init__.py,sha256=aQAsH9akPMBVf7F0KNiELAtb-yoThQsR41nXk3G_mbQ,943
|
19
|
+
parqv/views/base.py,sha256=9kRsUjTXdxEoiy9gSlNmU1ksZsN7FkyXWepX4C0vBFQ,2948
|
20
|
+
parqv/views/data_view.py,sha256=Xk7XdXCN82AmH3hyrmjDAIGtc212zc1lrqE_2ijWKeY,4344
|
21
|
+
parqv/views/metadata_view.py,sha256=6qdLEyO0te1TMiVtTxq-L3PWTOxCtFkkrDH_4yaXPaw,2058
|
22
|
+
parqv/views/schema_view.py,sha256=r1ZHJZ7g20KT_EfGrh71AU0-gNj1Z4Z-WUDQCwOu3BY,8966
|
23
|
+
parqv/views/components/__init__.py,sha256=rBsX9UH67GCLn275LhG2Xyf84gRtrm-otuj6zW5JAiU,267
|
24
|
+
parqv/views/components/enhanced_data_table.py,sha256=57pJD9rT-kiy2llw4p5pZOReUtv5CZxN-2tD6e7QDaI,4952
|
25
|
+
parqv/views/components/error_display.py,sha256=Ak1AbT9dikkF3izVtVFxdRB0nas6jqjb5tPG3l-_JjQ,2444
|
26
|
+
parqv/views/components/loading_display.py,sha256=1lLxvt5oMBGDiojODTVQ6O_hVNFHCWqpWamk863BMkY,1440
|
27
|
+
parqv/views/utils/__init__.py,sha256=3ZmzT2X0XC19Bp9MJOeWGpvWUQ6b_2xNjzI4WcxBARQ,515
|
28
|
+
parqv/views/utils/data_formatters.py,sha256=NDm0UhvfAGq5P0ovWL5GSchDG_YZOyXlTmhcifo_BjQ,5324
|
29
|
+
parqv/views/utils/stats_formatters.py,sha256=ZKrCPcs29lKAJ-w_cwpShvrVNWV6CY92LJOLrBKKP7U,7160
|
30
|
+
parqv/views/utils/visualization.py,sha256=JXe_gOk0mFvHXKAofT84ggQCIBLbBUckq0oolP9OaEk,7064
|
31
|
+
parqv-0.3.0.dist-info/licenses/LICENSE,sha256=Ewl2wCa8r6ncxHlpf-ZZXb77c82zdfxHuEeKzBbm6nM,11324
|
32
|
+
parqv-0.3.0.dist-info/METADATA,sha256=B3F_GkB224DykJkXZ0VzAc9v4beviGn2JBuwANqRksY,5382
|
33
|
+
parqv-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
34
|
+
parqv-0.3.0.dist-info/entry_points.txt,sha256=8Tm8rTiIB-tbVItoOA4M7seEmFnrtK25BMH9UKzqfXg,44
|
35
|
+
parqv-0.3.0.dist-info/top_level.txt,sha256=_t3_49ZluJbvl0QU_P3GNVuXxCffqiTp37dzZIa2GEw,6
|
36
|
+
parqv-0.3.0.dist-info/RECORD,,
|
parqv-0.2.0.dist-info/RECORD
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
parqv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
parqv/app.py,sha256=QmGEBtHoASdNzDmSgLQ2BvpTnZOuxQj2l0QGhYfp48w,6476
|
3
|
-
parqv/parqv.css,sha256=C42ZXUwMX1ZXfGo0AmixbHxz0CWKzWBHZ_hkhq5aehg,2920
|
4
|
-
parqv/handlers/__init__.py,sha256=gplqWWpt0npYosDZfoX1Ek0sfvAD0YITMjlQEo-IYVc,343
|
5
|
-
parqv/handlers/base_handler.py,sha256=JjlI0QdUZmozaMhAZnhzzS8nC2J63QRDX3rZqzXOpW8,3662
|
6
|
-
parqv/handlers/json.py,sha256=KgujRGimqzE3kVGQFwfkCb2Yv3YWxB8w-b17q5X2Yj8,19794
|
7
|
-
parqv/handlers/parquet.py,sha256=kG46oeVONmi6cCVzf5MaF88PkmXP7jCEGIAlDkG6N2M,32221
|
8
|
-
parqv/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
parqv/views/data_view.py,sha256=T_LPbXdxm_KOmwtTQWAxlUeNTlSGfIgQIg853WDMAME,2355
|
10
|
-
parqv/views/metadata_view.py,sha256=GnzhPJolMDZU8AAwY0h_uToz1sdM54iJbM2GLkD3caI,1013
|
11
|
-
parqv/views/schema_view.py,sha256=3QUYlOxTk9DYKdipEmt79U6hSlNJa_j3a3SK9UNwWHk,11332
|
12
|
-
parqv-0.2.0.dist-info/licenses/LICENSE,sha256=Ewl2wCa8r6ncxHlpf-ZZXb77c82zdfxHuEeKzBbm6nM,11324
|
13
|
-
parqv-0.2.0.dist-info/METADATA,sha256=q1K9Vqntt70-ffbJ3eT2g8_TW4XABsFHCkdYbBF4nXM,5387
|
14
|
-
parqv-0.2.0.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
15
|
-
parqv-0.2.0.dist-info/entry_points.txt,sha256=8Tm8rTiIB-tbVItoOA4M7seEmFnrtK25BMH9UKzqfXg,44
|
16
|
-
parqv-0.2.0.dist-info/top_level.txt,sha256=_t3_49ZluJbvl0QU_P3GNVuXxCffqiTp37dzZIa2GEw,6
|
17
|
-
parqv-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|