parqv 0.1.0__py3-none-any.whl → 0.2.1__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 +97 -78
- parqv/cli.py +112 -0
- parqv/core/__init__.py +31 -0
- parqv/core/config.py +25 -0
- parqv/core/file_utils.py +88 -0
- parqv/core/handler_factory.py +89 -0
- parqv/core/logging.py +46 -0
- parqv/data_sources/__init__.py +44 -0
- parqv/data_sources/base/__init__.py +28 -0
- parqv/data_sources/base/exceptions.py +38 -0
- parqv/data_sources/base/handler.py +143 -0
- parqv/data_sources/formats/__init__.py +16 -0
- parqv/data_sources/formats/json.py +449 -0
- parqv/data_sources/formats/parquet.py +624 -0
- 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 -13
- parqv/views/schema_view.py +197 -148
- parqv/views/utils/__init__.py +13 -0
- parqv/views/utils/data_formatters.py +162 -0
- parqv/views/utils/stats_formatters.py +160 -0
- parqv-0.2.1.dist-info/METADATA +104 -0
- parqv-0.2.1.dist-info/RECORD +34 -0
- {parqv-0.1.0.dist-info → parqv-0.2.1.dist-info}/WHEEL +1 -1
- parqv/parquet_handler.py +0 -389
- parqv/views/row_group_view.py +0 -33
- parqv-0.1.0.dist-info/METADATA +0 -91
- parqv-0.1.0.dist-info/RECORD +0 -15
- {parqv-0.1.0.dist-info → parqv-0.2.1.dist-info}/entry_points.txt +0 -0
- {parqv-0.1.0.dist-info → parqv-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {parqv-0.1.0.dist-info → parqv-0.2.1.dist-info}/top_level.txt +0 -0
parqv/views/base.py
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
"""
|
2
|
+
Base classes for parqv views.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
from textual.containers import Container
|
8
|
+
from textual.widgets import Static
|
9
|
+
|
10
|
+
from ..core import get_logger
|
11
|
+
from ..data_sources import DataHandler
|
12
|
+
|
13
|
+
|
14
|
+
class BaseView(Container):
|
15
|
+
"""
|
16
|
+
Base class for all parqv views.
|
17
|
+
|
18
|
+
Provides common functionality for data loading, error handling,
|
19
|
+
and handler access.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, **kwargs):
|
23
|
+
super().__init__(**kwargs)
|
24
|
+
self._is_mounted = False
|
25
|
+
|
26
|
+
@property
|
27
|
+
def logger(self):
|
28
|
+
"""Get a logger for this view."""
|
29
|
+
return get_logger(f"{self.__class__.__module__}.{self.__class__.__name__}")
|
30
|
+
|
31
|
+
@property
|
32
|
+
def handler(self) -> Optional[DataHandler]:
|
33
|
+
"""Get the data handler from the app."""
|
34
|
+
if hasattr(self.app, 'handler'):
|
35
|
+
return self.app.handler
|
36
|
+
return None
|
37
|
+
|
38
|
+
def on_mount(self) -> None:
|
39
|
+
"""Called when the view is mounted."""
|
40
|
+
self._is_mounted = True
|
41
|
+
self.load_content()
|
42
|
+
|
43
|
+
def load_content(self) -> None:
|
44
|
+
"""
|
45
|
+
Load the main content for this view. Must be implemented by subclasses.
|
46
|
+
|
47
|
+
Raises:
|
48
|
+
NotImplementedError: If not implemented by subclass
|
49
|
+
"""
|
50
|
+
raise NotImplementedError("Subclasses must implement load_content()")
|
51
|
+
|
52
|
+
def clear_content(self) -> None:
|
53
|
+
"""Clear all content from the view."""
|
54
|
+
try:
|
55
|
+
self.query("*").remove()
|
56
|
+
except Exception as e:
|
57
|
+
self.logger.error(f"Error clearing content: {e}")
|
58
|
+
|
59
|
+
def show_error(self, message: str, exception: Optional[Exception] = None) -> None:
|
60
|
+
"""
|
61
|
+
Display an error message in the view.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
message: Error message to display
|
65
|
+
exception: Optional exception that caused the error
|
66
|
+
"""
|
67
|
+
if exception:
|
68
|
+
self.logger.exception(f"Error in {self.__class__.__name__}: {message}")
|
69
|
+
else:
|
70
|
+
self.logger.error(f"Error in {self.__class__.__name__}: {message}")
|
71
|
+
|
72
|
+
self.clear_content()
|
73
|
+
error_widget = Static(f"[red]Error: {message}[/red]", classes="error-content")
|
74
|
+
self.mount(error_widget)
|
75
|
+
|
76
|
+
def show_info(self, message: str) -> None:
|
77
|
+
"""
|
78
|
+
Display an informational message in the view.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
message: Info message to display
|
82
|
+
"""
|
83
|
+
self.logger.info(f"Info in {self.__class__.__name__}: {message}")
|
84
|
+
self.clear_content()
|
85
|
+
info_widget = Static(f"[blue]Info: {message}[/blue]", classes="info-content")
|
86
|
+
self.mount(info_widget)
|
87
|
+
|
88
|
+
def check_handler_available(self) -> bool:
|
89
|
+
"""
|
90
|
+
Check if handler is available and show error if not.
|
91
|
+
|
92
|
+
Returns:
|
93
|
+
True if handler is available, False otherwise
|
94
|
+
"""
|
95
|
+
if not self.handler:
|
96
|
+
self.show_error("Data handler not available")
|
97
|
+
return False
|
98
|
+
return True
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""
|
2
|
+
Reusable UI components for parqv views.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from .error_display import ErrorDisplay
|
6
|
+
from .loading_display import LoadingDisplay
|
7
|
+
from .enhanced_data_table import EnhancedDataTable
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"ErrorDisplay",
|
11
|
+
"LoadingDisplay",
|
12
|
+
"EnhancedDataTable",
|
13
|
+
]
|
@@ -0,0 +1,152 @@
|
|
1
|
+
"""
|
2
|
+
Enhanced data table component for parqv views.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Optional, List, Tuple, Any
|
6
|
+
|
7
|
+
import pandas as pd
|
8
|
+
from textual.containers import Container
|
9
|
+
from textual.widgets import DataTable, Static
|
10
|
+
|
11
|
+
from ...core import get_logger
|
12
|
+
|
13
|
+
log = get_logger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class EnhancedDataTable(Container):
|
17
|
+
"""
|
18
|
+
An enhanced data table component that handles DataFrame display with better error handling.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self, **kwargs):
|
22
|
+
super().__init__(**kwargs)
|
23
|
+
self._table: Optional[DataTable] = None
|
24
|
+
|
25
|
+
def compose(self):
|
26
|
+
"""Compose the data table layout."""
|
27
|
+
self._table = DataTable(id="enhanced-data-table")
|
28
|
+
self._table.cursor_type = "row"
|
29
|
+
yield self._table
|
30
|
+
|
31
|
+
def clear_table(self) -> bool:
|
32
|
+
"""
|
33
|
+
Clear the table contents safely.
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
True if cleared successfully, False if recreation was needed
|
37
|
+
"""
|
38
|
+
if not self._table:
|
39
|
+
return False
|
40
|
+
|
41
|
+
try:
|
42
|
+
self._table.clear(columns=True)
|
43
|
+
return True
|
44
|
+
except Exception as e:
|
45
|
+
log.warning(f"Failed to clear table, recreating: {e}")
|
46
|
+
return self._recreate_table()
|
47
|
+
|
48
|
+
def _recreate_table(self) -> bool:
|
49
|
+
"""
|
50
|
+
Recreate the table if clearing failed.
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
True if recreation was successful, False otherwise
|
54
|
+
"""
|
55
|
+
try:
|
56
|
+
if self._table:
|
57
|
+
self._table.remove()
|
58
|
+
|
59
|
+
self._table = DataTable(id="enhanced-data-table")
|
60
|
+
self._table.cursor_type = "row"
|
61
|
+
self.mount(self._table)
|
62
|
+
return True
|
63
|
+
except Exception as e:
|
64
|
+
log.error(f"Failed to recreate table: {e}")
|
65
|
+
return False
|
66
|
+
|
67
|
+
def load_dataframe(self, df: pd.DataFrame, max_rows: Optional[int] = None) -> bool:
|
68
|
+
"""
|
69
|
+
Load a pandas DataFrame into the table.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
df: The DataFrame to load
|
73
|
+
max_rows: Optional maximum number of rows to display
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
True if loaded successfully, False otherwise
|
77
|
+
"""
|
78
|
+
if not self._table:
|
79
|
+
log.error("Table not initialized")
|
80
|
+
return False
|
81
|
+
|
82
|
+
try:
|
83
|
+
# Clear existing content
|
84
|
+
if not self.clear_table():
|
85
|
+
return False
|
86
|
+
|
87
|
+
# Handle empty DataFrame
|
88
|
+
if df.empty:
|
89
|
+
self._show_empty_message()
|
90
|
+
return True
|
91
|
+
|
92
|
+
# Limit rows if specified
|
93
|
+
display_df = df.head(max_rows) if max_rows else df
|
94
|
+
|
95
|
+
# Add columns
|
96
|
+
columns = [str(col) for col in display_df.columns]
|
97
|
+
self._table.add_columns(*columns)
|
98
|
+
|
99
|
+
# Add rows
|
100
|
+
rows_data = self._prepare_rows_data(display_df)
|
101
|
+
self._table.add_rows(rows_data)
|
102
|
+
|
103
|
+
log.info(f"Loaded {len(display_df)} rows and {len(columns)} columns into table")
|
104
|
+
return True
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
log.exception(f"Error loading DataFrame into table: {e}")
|
108
|
+
self._show_error_message(f"Failed to load data: {e}")
|
109
|
+
return False
|
110
|
+
|
111
|
+
def _prepare_rows_data(self, df: pd.DataFrame) -> List[Tuple[str, ...]]:
|
112
|
+
"""
|
113
|
+
Prepare DataFrame rows for the DataTable.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
df: The DataFrame to process
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
List of tuples representing table rows
|
120
|
+
"""
|
121
|
+
rows_data = []
|
122
|
+
for row in df.itertuples(index=False, name=None):
|
123
|
+
# Convert each item to string, handling NaN values
|
124
|
+
row_strings = tuple(
|
125
|
+
str(item) if pd.notna(item) else ""
|
126
|
+
for item in row
|
127
|
+
)
|
128
|
+
rows_data.append(row_strings)
|
129
|
+
return rows_data
|
130
|
+
|
131
|
+
def _show_empty_message(self) -> None:
|
132
|
+
"""Show a message when the DataFrame is empty."""
|
133
|
+
try:
|
134
|
+
self.query("Static").remove() # Remove any existing messages
|
135
|
+
empty_msg = Static("No data available in the selected range or file is empty.",
|
136
|
+
classes="info-content")
|
137
|
+
self.mount(empty_msg)
|
138
|
+
except Exception as e:
|
139
|
+
log.error(f"Failed to show empty message: {e}")
|
140
|
+
|
141
|
+
def _show_error_message(self, message: str) -> None:
|
142
|
+
"""Show an error message in the table area."""
|
143
|
+
try:
|
144
|
+
self.query("DataTable, Static").remove() # Remove table and any messages
|
145
|
+
error_msg = Static(f"[red]{message}[/red]", classes="error-content")
|
146
|
+
self.mount(error_msg)
|
147
|
+
except Exception as e:
|
148
|
+
log.error(f"Failed to show error message: {e}")
|
149
|
+
|
150
|
+
def get_table(self) -> Optional[DataTable]:
|
151
|
+
"""Get the underlying DataTable widget."""
|
152
|
+
return self._table
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"""
|
2
|
+
Error display component for parqv views.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
from textual.containers import VerticalScroll
|
8
|
+
from textual.widgets import Static, Label
|
9
|
+
|
10
|
+
|
11
|
+
class ErrorDisplay(VerticalScroll):
|
12
|
+
"""
|
13
|
+
A reusable component for displaying error messages in a consistent format.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self,
|
17
|
+
title: str = "Error",
|
18
|
+
message: str = "An error occurred",
|
19
|
+
details: Optional[str] = None,
|
20
|
+
**kwargs):
|
21
|
+
"""
|
22
|
+
Initialize the error display.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
title: Error title/category
|
26
|
+
message: Main error message
|
27
|
+
details: Optional detailed error information
|
28
|
+
**kwargs: Additional arguments for VerticalScroll
|
29
|
+
"""
|
30
|
+
super().__init__(**kwargs)
|
31
|
+
self.title = title
|
32
|
+
self.message = message
|
33
|
+
self.details = details
|
34
|
+
|
35
|
+
def compose(self):
|
36
|
+
"""Compose the error display layout."""
|
37
|
+
yield Label(self.title, classes="error-title")
|
38
|
+
yield Static(f"[red]{self.message}[/red]", classes="error-content")
|
39
|
+
|
40
|
+
if self.details:
|
41
|
+
yield Static("Details:", classes="error-details-label")
|
42
|
+
yield Static(f"[dim]{self.details}[/dim]", classes="error-details")
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def file_not_found(cls, file_path: str, **kwargs) -> 'ErrorDisplay':
|
46
|
+
"""Create an error display for file not found errors."""
|
47
|
+
return cls(
|
48
|
+
title="File Not Found",
|
49
|
+
message=f"Could not find file: {file_path}",
|
50
|
+
details="Please check that the file path is correct and the file exists.",
|
51
|
+
**kwargs
|
52
|
+
)
|
53
|
+
|
54
|
+
@classmethod
|
55
|
+
def handler_not_available(cls, **kwargs) -> 'ErrorDisplay':
|
56
|
+
"""Create an error display for missing data handler."""
|
57
|
+
return cls(
|
58
|
+
title="Data Handler Not Available",
|
59
|
+
message="No data handler is currently loaded",
|
60
|
+
details="This usually means the file could not be processed or loaded.",
|
61
|
+
**kwargs
|
62
|
+
)
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def data_loading_error(cls, error_msg: str, **kwargs) -> 'ErrorDisplay':
|
66
|
+
"""Create an error display for data loading errors."""
|
67
|
+
return cls(
|
68
|
+
title="Data Loading Error",
|
69
|
+
message="Failed to load data from the file",
|
70
|
+
details=f"Technical details: {error_msg}",
|
71
|
+
**kwargs
|
72
|
+
)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
"""
|
2
|
+
Loading display component for parqv views.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from textual.containers import Center, Middle
|
6
|
+
from textual.widgets import LoadingIndicator, Label
|
7
|
+
|
8
|
+
|
9
|
+
class LoadingDisplay(Center):
|
10
|
+
"""
|
11
|
+
A reusable component for displaying loading states in a consistent format.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def __init__(self, message: str = "Loading...", **kwargs):
|
15
|
+
"""
|
16
|
+
Initialize the loading display.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
message: Loading message to display
|
20
|
+
**kwargs: Additional arguments for Center container
|
21
|
+
"""
|
22
|
+
super().__init__(**kwargs)
|
23
|
+
self.message = message
|
24
|
+
|
25
|
+
def compose(self):
|
26
|
+
"""Compose the loading display layout."""
|
27
|
+
with Middle():
|
28
|
+
yield LoadingIndicator()
|
29
|
+
yield Label(self.message, classes="loading-message")
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
def data_loading(cls, **kwargs) -> 'LoadingDisplay':
|
33
|
+
"""Create a loading display for data loading operations."""
|
34
|
+
return cls(message="Loading data...", **kwargs)
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def metadata_loading(cls, **kwargs) -> 'LoadingDisplay':
|
38
|
+
"""Create a loading display for metadata loading operations."""
|
39
|
+
return cls(message="Loading metadata...", **kwargs)
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def schema_loading(cls, **kwargs) -> 'LoadingDisplay':
|
43
|
+
"""Create a loading display for schema loading operations."""
|
44
|
+
return cls(message="Loading schema...", **kwargs)
|
parqv/views/data_view.py
CHANGED
@@ -1,68 +1,141 @@
|
|
1
|
-
|
1
|
+
"""
|
2
|
+
Data view for displaying tabular data preview.
|
3
|
+
"""
|
4
|
+
|
2
5
|
from typing import Optional
|
3
6
|
|
4
7
|
import pandas as pd
|
5
8
|
from textual.app import ComposeResult
|
6
|
-
from textual.containers import Container
|
7
|
-
from textual.widgets import DataTable, Static
|
8
9
|
|
9
|
-
|
10
|
+
from .base import BaseView
|
11
|
+
from .components import EnhancedDataTable
|
12
|
+
from ..core import DEFAULT_PREVIEW_ROWS
|
13
|
+
|
10
14
|
|
15
|
+
class DataView(BaseView):
|
16
|
+
"""
|
17
|
+
View for displaying a preview of the data in tabular format.
|
18
|
+
|
19
|
+
Shows the first N rows of data in an interactive table format
|
20
|
+
with proper error handling and loading states.
|
21
|
+
"""
|
11
22
|
|
12
|
-
|
13
|
-
|
23
|
+
def __init__(self, preview_rows: int = DEFAULT_PREVIEW_ROWS, **kwargs):
|
24
|
+
"""
|
25
|
+
Initialize the data view.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
preview_rows: Number of rows to show in preview
|
29
|
+
**kwargs: Additional arguments for BaseView
|
30
|
+
"""
|
31
|
+
super().__init__(**kwargs)
|
32
|
+
self.preview_rows = preview_rows
|
33
|
+
self._data_table: Optional[EnhancedDataTable] = None
|
14
34
|
|
15
35
|
def compose(self) -> ComposeResult:
|
16
|
-
|
36
|
+
"""Compose the data view layout."""
|
37
|
+
self._data_table = EnhancedDataTable(id="data-preview-table")
|
38
|
+
yield self._data_table
|
17
39
|
|
18
|
-
def
|
19
|
-
|
40
|
+
def load_content(self) -> None:
|
41
|
+
"""Load and display data content."""
|
42
|
+
if not self.check_handler_available():
|
43
|
+
return
|
20
44
|
|
21
|
-
|
22
|
-
|
45
|
+
if not self._data_table:
|
46
|
+
self.show_error("Data table component not initialized")
|
47
|
+
return
|
23
48
|
|
24
49
|
try:
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
self.
|
32
|
-
except Exception as remount_e:
|
33
|
-
log.error(f"Failed to remount DataTable: {remount_e}")
|
34
|
-
self.mount(Static("[red]Error initializing data table.[/red]", classes="error-content"))
|
50
|
+
# Get data preview from handler
|
51
|
+
self.logger.info(f"Loading data preview ({self.preview_rows} rows)")
|
52
|
+
df = self.handler.get_data_preview(num_rows=self.preview_rows)
|
53
|
+
|
54
|
+
# Validate DataFrame
|
55
|
+
if df is None:
|
56
|
+
self.show_error("Could not load data preview - handler returned None")
|
35
57
|
return
|
36
58
|
|
37
|
-
|
38
|
-
if
|
39
|
-
self.
|
59
|
+
# Handle error DataFrame (some handlers return error as DataFrame)
|
60
|
+
if self._is_error_dataframe(df):
|
61
|
+
error_msg = self._extract_error_from_dataframe(df)
|
62
|
+
self.show_error(error_msg)
|
40
63
|
return
|
41
64
|
|
42
|
-
|
65
|
+
# Load DataFrame into table
|
66
|
+
success = self._data_table.load_dataframe(df, max_rows=self.preview_rows)
|
43
67
|
|
44
|
-
if
|
45
|
-
self.
|
46
|
-
|
68
|
+
if success:
|
69
|
+
self.logger.info(f"Data preview loaded successfully: {len(df)} rows")
|
70
|
+
else:
|
71
|
+
self.show_error("Failed to load data into table component")
|
47
72
|
|
48
|
-
|
49
|
-
|
50
|
-
|
73
|
+
except Exception as e:
|
74
|
+
self.show_error("Failed to load data preview", e)
|
75
|
+
|
76
|
+
def _is_error_dataframe(self, df: pd.DataFrame) -> bool:
|
77
|
+
"""
|
78
|
+
Check if the DataFrame represents an error condition.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
df: DataFrame to check
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
True if the DataFrame contains error information
|
85
|
+
"""
|
86
|
+
return (
|
87
|
+
not df.empty and
|
88
|
+
"error" in df.columns and
|
89
|
+
len(df.columns) == 1
|
90
|
+
)
|
91
|
+
|
92
|
+
def _extract_error_from_dataframe(self, df: pd.DataFrame) -> str:
|
93
|
+
"""
|
94
|
+
Extract error message from an error DataFrame.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
df: Error DataFrame
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
Error message string
|
101
|
+
"""
|
102
|
+
try:
|
103
|
+
if not df.empty and "error" in df.columns:
|
104
|
+
return str(df["error"].iloc[0])
|
105
|
+
except Exception:
|
106
|
+
pass
|
107
|
+
return "Unknown error in data loading"
|
108
|
+
|
109
|
+
def refresh_data(self) -> None:
|
110
|
+
"""Refresh the data display."""
|
111
|
+
self.clear_content()
|
112
|
+
self.load_content()
|
51
113
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
114
|
+
def set_preview_rows(self, new_rows: int) -> None:
|
115
|
+
"""
|
116
|
+
Update the number of preview rows and refresh display.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
new_rows: New number of rows to preview
|
120
|
+
"""
|
121
|
+
if new_rows > 0:
|
122
|
+
self.preview_rows = new_rows
|
123
|
+
self.refresh_data()
|
124
|
+
else:
|
125
|
+
self.logger.warning(f"Invalid preview_rows value: {new_rows}")
|
61
126
|
|
127
|
+
def get_current_data(self) -> Optional[pd.DataFrame]:
|
128
|
+
"""
|
129
|
+
Get the currently displayed data if available.
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
Currently loaded DataFrame or None
|
133
|
+
"""
|
134
|
+
if not self.handler:
|
135
|
+
return None
|
136
|
+
|
137
|
+
try:
|
138
|
+
return self.handler.get_data_preview(num_rows=self.preview_rows)
|
62
139
|
except Exception as e:
|
63
|
-
|
64
|
-
|
65
|
-
self.query("DataTable, Static").remove()
|
66
|
-
self.mount(Static(f"Error loading data preview: {e}", classes="error-content"))
|
67
|
-
except Exception as display_e:
|
68
|
-
log.error(f"Error displaying error message: {display_e}")
|
140
|
+
self.logger.error(f"Failed to get current data: {e}")
|
141
|
+
return None
|
parqv/views/metadata_view.py
CHANGED
@@ -1,19 +1,63 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
"""
|
2
|
+
Metadata view for displaying file metadata information.
|
3
|
+
"""
|
3
4
|
|
5
|
+
from textual.containers import VerticalScroll
|
6
|
+
from textual.widgets import Pretty
|
4
7
|
|
5
|
-
|
8
|
+
from .base import BaseView
|
9
|
+
from .components import ErrorDisplay
|
10
|
+
from .utils import format_metadata_for_display
|
6
11
|
|
7
|
-
def on_mount(self) -> None:
|
8
|
-
self.load_metadata()
|
9
12
|
|
10
|
-
|
13
|
+
class MetadataView(BaseView):
|
14
|
+
"""
|
15
|
+
View for displaying metadata information about the loaded file.
|
16
|
+
|
17
|
+
Shows file statistics, format information, and other metadata
|
18
|
+
in a formatted display.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def load_content(self) -> None:
|
22
|
+
"""Load and display metadata content."""
|
23
|
+
if not self.check_handler_available():
|
24
|
+
return
|
25
|
+
|
26
|
+
try:
|
27
|
+
# Get raw metadata from handler
|
28
|
+
raw_metadata = self.handler.get_metadata_summary()
|
29
|
+
|
30
|
+
# Format metadata for display
|
31
|
+
formatted_metadata = format_metadata_for_display(raw_metadata)
|
32
|
+
|
33
|
+
# Check if there's an error in the formatted data
|
34
|
+
if "Error" in formatted_metadata and len(formatted_metadata) == 1:
|
35
|
+
self.show_error(formatted_metadata["Error"])
|
36
|
+
return
|
37
|
+
|
38
|
+
# Display the formatted metadata
|
39
|
+
self._display_metadata(formatted_metadata)
|
40
|
+
|
41
|
+
self.logger.info("Metadata loaded successfully")
|
42
|
+
|
43
|
+
except Exception as e:
|
44
|
+
self.show_error("Failed to load metadata", e)
|
45
|
+
|
46
|
+
def _display_metadata(self, metadata: dict) -> None:
|
47
|
+
"""
|
48
|
+
Display the formatted metadata using Pretty widget.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
metadata: Formatted metadata dictionary
|
52
|
+
"""
|
11
53
|
try:
|
12
|
-
|
13
|
-
|
14
|
-
pretty_widget = Pretty(meta_data)
|
15
|
-
self.mount(pretty_widget)
|
16
|
-
else:
|
17
|
-
self.mount(Static("Parquet handler not available.", classes="error-content"))
|
54
|
+
pretty_widget = Pretty(metadata, id="metadata-pretty")
|
55
|
+
self.mount(pretty_widget)
|
18
56
|
except Exception as e:
|
19
|
-
self.
|
57
|
+
self.logger.error(f"Failed to create Pretty widget: {e}")
|
58
|
+
self.show_error("Failed to display metadata")
|
59
|
+
|
60
|
+
def refresh_metadata(self) -> None:
|
61
|
+
"""Refresh the metadata display."""
|
62
|
+
self.clear_content()
|
63
|
+
self.load_content()
|