parqv 0.1.0__tar.gz → 0.2.1__tar.gz
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-0.2.1/PKG-INFO +104 -0
- parqv-0.2.1/README.md +88 -0
- {parqv-0.1.0 → parqv-0.2.1}/pyproject.toml +4 -3
- parqv-0.2.1/src/parqv/__init__.py +31 -0
- parqv-0.2.1/src/parqv/app.py +150 -0
- parqv-0.2.1/src/parqv/cli.py +112 -0
- parqv-0.2.1/src/parqv/core/__init__.py +31 -0
- parqv-0.2.1/src/parqv/core/config.py +25 -0
- parqv-0.2.1/src/parqv/core/file_utils.py +88 -0
- parqv-0.2.1/src/parqv/core/handler_factory.py +89 -0
- parqv-0.2.1/src/parqv/core/logging.py +46 -0
- parqv-0.2.1/src/parqv/data_sources/__init__.py +44 -0
- parqv-0.2.1/src/parqv/data_sources/base/__init__.py +28 -0
- parqv-0.2.1/src/parqv/data_sources/base/exceptions.py +38 -0
- parqv-0.2.1/src/parqv/data_sources/base/handler.py +143 -0
- parqv-0.2.1/src/parqv/data_sources/formats/__init__.py +16 -0
- parqv-0.2.1/src/parqv/data_sources/formats/json.py +449 -0
- parqv-0.2.1/src/parqv/data_sources/formats/parquet.py +624 -0
- parqv-0.2.1/src/parqv/views/__init__.py +38 -0
- parqv-0.2.1/src/parqv/views/base.py +98 -0
- parqv-0.2.1/src/parqv/views/components/__init__.py +13 -0
- parqv-0.2.1/src/parqv/views/components/enhanced_data_table.py +152 -0
- parqv-0.2.1/src/parqv/views/components/error_display.py +72 -0
- parqv-0.2.1/src/parqv/views/components/loading_display.py +44 -0
- parqv-0.2.1/src/parqv/views/data_view.py +141 -0
- parqv-0.2.1/src/parqv/views/metadata_view.py +63 -0
- parqv-0.2.1/src/parqv/views/schema_view.py +236 -0
- parqv-0.2.1/src/parqv/views/utils/__init__.py +13 -0
- parqv-0.2.1/src/parqv/views/utils/data_formatters.py +162 -0
- parqv-0.2.1/src/parqv/views/utils/stats_formatters.py +160 -0
- parqv-0.2.1/src/parqv.egg-info/PKG-INFO +104 -0
- parqv-0.2.1/src/parqv.egg-info/SOURCES.txt +37 -0
- {parqv-0.1.0 → parqv-0.2.1}/src/parqv.egg-info/requires.txt +1 -0
- parqv-0.1.0/PKG-INFO +0 -91
- parqv-0.1.0/README.md +0 -76
- parqv-0.1.0/src/parqv/__init__.py +0 -0
- parqv-0.1.0/src/parqv/app.py +0 -131
- parqv-0.1.0/src/parqv/parquet_handler.py +0 -389
- parqv-0.1.0/src/parqv/views/__init__.py +0 -0
- parqv-0.1.0/src/parqv/views/data_view.py +0 -68
- parqv-0.1.0/src/parqv/views/metadata_view.py +0 -19
- parqv-0.1.0/src/parqv/views/row_group_view.py +0 -33
- parqv-0.1.0/src/parqv/views/schema_view.py +0 -187
- parqv-0.1.0/src/parqv.egg-info/PKG-INFO +0 -91
- parqv-0.1.0/src/parqv.egg-info/SOURCES.txt +0 -18
- {parqv-0.1.0 → parqv-0.2.1}/LICENSE +0 -0
- {parqv-0.1.0 → parqv-0.2.1}/setup.cfg +0 -0
- {parqv-0.1.0 → parqv-0.2.1}/src/parqv/parqv.css +0 -0
- {parqv-0.1.0 → parqv-0.2.1}/src/parqv.egg-info/dependency_links.txt +0 -0
- {parqv-0.1.0 → parqv-0.2.1}/src/parqv.egg-info/entry_points.txt +0 -0
- {parqv-0.1.0 → parqv-0.2.1}/src/parqv.egg-info/top_level.txt +0 -0
parqv-0.2.1/PKG-INFO
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: parqv
|
3
|
+
Version: 0.2.1
|
4
|
+
Summary: An interactive Python TUI for visualizing, exploring, and analyzing files directly in your terminal.
|
5
|
+
Author-email: Sangmin Yoon <sanspareilsmyn@gmail.com>
|
6
|
+
License-Expression: Apache-2.0
|
7
|
+
Requires-Python: >=3.10
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
License-File: LICENSE
|
10
|
+
Requires-Dist: textual>=1.0.0
|
11
|
+
Requires-Dist: pyarrow>=16.0.0
|
12
|
+
Requires-Dist: pandas>=2.0.0
|
13
|
+
Requires-Dist: numpy>=1.20.0
|
14
|
+
Requires-Dist: duckdb>=1.2.0
|
15
|
+
Dynamic: license-file
|
16
|
+
|
17
|
+
# parqv
|
18
|
+
|
19
|
+
[](https://www.python.org/)
|
20
|
+
[](LICENSE)
|
21
|
+
[](https://badge.fury.io/py/parqv) <!-- TODO: Link after first PyPI release -->
|
22
|
+
[](https://textual.textualize.io/)
|
23
|
+
|
24
|
+
---
|
25
|
+
|
26
|
+
**Supported File Formats:** ✅ **Parquet** | ✅ **JSON** / **JSON Lines (ndjson)** | *(More planned!)*
|
27
|
+
|
28
|
+
---
|
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.** Initially supporting Parquet and JSON, `parqv` aims to provide a unified, visual experience for quick data inspection without leaving your console.
|
31
|
+
|
32
|
+
## 💻 Demo
|
33
|
+
|
34
|
+

|
35
|
+
*(Demo shows Parquet features; UI adapts for other formats)*
|
36
|
+
|
37
|
+
## 🤔 Why `parqv`?
|
38
|
+
1. **Unified Interface:** Launch `parqv <your_data_file>` to access **metadata, schema, data preview, and column statistics** all within a single, navigable terminal window. No more juggling different commands for different file types.
|
39
|
+
2. **Interactive Exploration:**
|
40
|
+
* **🖱️ Keyboard & Mouse Driven:** Navigate using familiar keys (arrows, `hjkl`, Tab) or even your mouse (thanks to `Textual`).
|
41
|
+
* **📜 Scrollable Views:** Easily scroll through large schemas, data tables, or column lists.
|
42
|
+
* **🌲 Clear Schema View:** Understand column names, data types, and nullability at a glance. (Complex nested structures visualization might vary by format).
|
43
|
+
* **📊 Dynamic Stats:** Select a column and instantly see its detailed statistics (counts, nulls, min/max, mean, distinct values, etc.).
|
44
|
+
3. **Cross-Format Consistency:**
|
45
|
+
* **🎨 Rich Display:** Leverages `rich` and `Textual` for colorful, readable tables and text across supported formats.
|
46
|
+
* **📈 Quick Stats:** Get key statistical insights consistently, regardless of the underlying file type.
|
47
|
+
* **🔌 Extensible:** Designed with a handler interface to easily add support for more file formats in the future (like CSV, Arrow IPC, etc.).
|
48
|
+
|
49
|
+
## ✨ Features (TUI Mode)
|
50
|
+
* **Multi-Format Support:** Currently supports **Parquet** (`.parquet`) and **JSON/JSON Lines** (`.json`, `.ndjson`). Run `parqv <your_file.{parquet,json,ndjson}>`.
|
51
|
+
* **Metadata Panel:** Displays key file information (path, format, size, total rows, column count, etc.). *Fields may vary slightly depending on the file format.*
|
52
|
+
* **Schema Explorer:**
|
53
|
+
* Interactive list view of columns.
|
54
|
+
* Clearly shows column names, data types, and nullability.
|
55
|
+
* **Data Table Viewer:**
|
56
|
+
* Scrollable table preview of the file's data.
|
57
|
+
* Attempts to preserve data types for better representation.
|
58
|
+
* **Column Statistics Viewer:**
|
59
|
+
* Select a column in the Schema tab to view detailed statistics.
|
60
|
+
* Shows counts (total, valid, null), percentages, and type-specific stats (min/max, mean, stddev, distinct counts, length stats, boolean value counts where applicable).
|
61
|
+
* **Row Group Inspector (Parquet Specific):**
|
62
|
+
* *This panel only appears when viewing Parquet files.*
|
63
|
+
* Lists row groups with stats (row count, compressed/uncompressed size).
|
64
|
+
* (Planned) Select a row group for more details.
|
65
|
+
|
66
|
+
## 🚀 Getting Started
|
67
|
+
|
68
|
+
**1. Prerequisites:**
|
69
|
+
* **Python:** Version 3.10 or higher.
|
70
|
+
* **pip:** The Python package installer.
|
71
|
+
|
72
|
+
**2. Install `parqv`:**
|
73
|
+
* Open your terminal and run:
|
74
|
+
```bash
|
75
|
+
pip install parqv
|
76
|
+
```
|
77
|
+
*(This will also install dependencies like `textual`, `pyarrow`, `pandas`, and `duckdb`)*
|
78
|
+
* **Updating `parqv`:**
|
79
|
+
```bash
|
80
|
+
pip install --upgrade parqv
|
81
|
+
```
|
82
|
+
|
83
|
+
**3. Run `parqv`:**
|
84
|
+
* Point `parqv` to your data file:
|
85
|
+
```bash
|
86
|
+
#parquet
|
87
|
+
parqv /path/to/your/data.parquet
|
88
|
+
|
89
|
+
# json
|
90
|
+
parqv /path/to/your/data.json
|
91
|
+
* The interactive TUI will launch. Use your keyboard (and mouse, if supported by your terminal) to navigate:
|
92
|
+
* **Arrow Keys / `j`,`k` (in lists):** Move selection up/down.
|
93
|
+
* **`Tab` / `Shift+Tab`:** Cycle focus between the main tab content and potentially other areas. (Focus handling might evolve).
|
94
|
+
* **`Enter` (in column list):** Select a column to view statistics.
|
95
|
+
* **View Switching:** Use `Ctrl+N` (Next Tab) and `Ctrl+P` (Previous Tab) or click on the tabs (Metadata, Schema, Data Preview).
|
96
|
+
* **Scrolling:** Use `PageUp` / `PageDown` / `Home` / `End` or arrow keys/mouse wheel within scrollable areas (like Schema stats or Data Preview).
|
97
|
+
* **`q` / `Ctrl+C`:** Quit `parqv`.
|
98
|
+
* *(Help Screen `?` is planned)*
|
99
|
+
|
100
|
+
---
|
101
|
+
|
102
|
+
## 📄 License
|
103
|
+
|
104
|
+
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text.
|
parqv-0.2.1/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# parqv
|
2
|
+
|
3
|
+
[](https://www.python.org/)
|
4
|
+
[](LICENSE)
|
5
|
+
[](https://badge.fury.io/py/parqv) <!-- TODO: Link after first PyPI release -->
|
6
|
+
[](https://textual.textualize.io/)
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
**Supported File Formats:** ✅ **Parquet** | ✅ **JSON** / **JSON Lines (ndjson)** | *(More planned!)*
|
11
|
+
|
12
|
+
---
|
13
|
+
|
14
|
+
**`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.** Initially supporting Parquet and JSON, `parqv` aims to provide a unified, visual experience for quick data inspection without leaving your console.
|
15
|
+
|
16
|
+
## 💻 Demo
|
17
|
+
|
18
|
+

|
19
|
+
*(Demo shows Parquet features; UI adapts for other formats)*
|
20
|
+
|
21
|
+
## 🤔 Why `parqv`?
|
22
|
+
1. **Unified Interface:** Launch `parqv <your_data_file>` to access **metadata, schema, data preview, and column statistics** all within a single, navigable terminal window. No more juggling different commands for different file types.
|
23
|
+
2. **Interactive Exploration:**
|
24
|
+
* **🖱️ Keyboard & Mouse Driven:** Navigate using familiar keys (arrows, `hjkl`, Tab) or even your mouse (thanks to `Textual`).
|
25
|
+
* **📜 Scrollable Views:** Easily scroll through large schemas, data tables, or column lists.
|
26
|
+
* **🌲 Clear Schema View:** Understand column names, data types, and nullability at a glance. (Complex nested structures visualization might vary by format).
|
27
|
+
* **📊 Dynamic Stats:** Select a column and instantly see its detailed statistics (counts, nulls, min/max, mean, distinct values, etc.).
|
28
|
+
3. **Cross-Format Consistency:**
|
29
|
+
* **🎨 Rich Display:** Leverages `rich` and `Textual` for colorful, readable tables and text across supported formats.
|
30
|
+
* **📈 Quick Stats:** Get key statistical insights consistently, regardless of the underlying file type.
|
31
|
+
* **🔌 Extensible:** Designed with a handler interface to easily add support for more file formats in the future (like CSV, Arrow IPC, etc.).
|
32
|
+
|
33
|
+
## ✨ Features (TUI Mode)
|
34
|
+
* **Multi-Format Support:** Currently supports **Parquet** (`.parquet`) and **JSON/JSON Lines** (`.json`, `.ndjson`). Run `parqv <your_file.{parquet,json,ndjson}>`.
|
35
|
+
* **Metadata Panel:** Displays key file information (path, format, size, total rows, column count, etc.). *Fields may vary slightly depending on the file format.*
|
36
|
+
* **Schema Explorer:**
|
37
|
+
* Interactive list view of columns.
|
38
|
+
* Clearly shows column names, data types, and nullability.
|
39
|
+
* **Data Table Viewer:**
|
40
|
+
* Scrollable table preview of the file's data.
|
41
|
+
* Attempts to preserve data types for better representation.
|
42
|
+
* **Column Statistics Viewer:**
|
43
|
+
* Select a column in the Schema tab to view detailed statistics.
|
44
|
+
* Shows counts (total, valid, null), percentages, and type-specific stats (min/max, mean, stddev, distinct counts, length stats, boolean value counts where applicable).
|
45
|
+
* **Row Group Inspector (Parquet Specific):**
|
46
|
+
* *This panel only appears when viewing Parquet files.*
|
47
|
+
* Lists row groups with stats (row count, compressed/uncompressed size).
|
48
|
+
* (Planned) Select a row group for more details.
|
49
|
+
|
50
|
+
## 🚀 Getting Started
|
51
|
+
|
52
|
+
**1. Prerequisites:**
|
53
|
+
* **Python:** Version 3.10 or higher.
|
54
|
+
* **pip:** The Python package installer.
|
55
|
+
|
56
|
+
**2. Install `parqv`:**
|
57
|
+
* Open your terminal and run:
|
58
|
+
```bash
|
59
|
+
pip install parqv
|
60
|
+
```
|
61
|
+
*(This will also install dependencies like `textual`, `pyarrow`, `pandas`, and `duckdb`)*
|
62
|
+
* **Updating `parqv`:**
|
63
|
+
```bash
|
64
|
+
pip install --upgrade parqv
|
65
|
+
```
|
66
|
+
|
67
|
+
**3. Run `parqv`:**
|
68
|
+
* Point `parqv` to your data file:
|
69
|
+
```bash
|
70
|
+
#parquet
|
71
|
+
parqv /path/to/your/data.parquet
|
72
|
+
|
73
|
+
# json
|
74
|
+
parqv /path/to/your/data.json
|
75
|
+
* The interactive TUI will launch. Use your keyboard (and mouse, if supported by your terminal) to navigate:
|
76
|
+
* **Arrow Keys / `j`,`k` (in lists):** Move selection up/down.
|
77
|
+
* **`Tab` / `Shift+Tab`:** Cycle focus between the main tab content and potentially other areas. (Focus handling might evolve).
|
78
|
+
* **`Enter` (in column list):** Select a column to view statistics.
|
79
|
+
* **View Switching:** Use `Ctrl+N` (Next Tab) and `Ctrl+P` (Previous Tab) or click on the tabs (Metadata, Schema, Data Preview).
|
80
|
+
* **Scrolling:** Use `PageUp` / `PageDown` / `Home` / `End` or arrow keys/mouse wheel within scrollable areas (like Schema stats or Data Preview).
|
81
|
+
* **`q` / `Ctrl+C`:** Quit `parqv`.
|
82
|
+
* *(Help Screen `?` is planned)*
|
83
|
+
|
84
|
+
---
|
85
|
+
|
86
|
+
## 📄 License
|
87
|
+
|
88
|
+
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text.
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "parqv"
|
7
|
-
version = "0.1
|
8
|
-
description = "An interactive Python TUI for visualizing, exploring, and analyzing
|
7
|
+
version = "0.2.1"
|
8
|
+
description = "An interactive Python TUI for visualizing, exploring, and analyzing files directly in your terminal."
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.10"
|
11
11
|
license = "Apache-2.0"
|
@@ -15,7 +15,8 @@ dependencies = [
|
|
15
15
|
"textual>=1.0.0",
|
16
16
|
"pyarrow>=16.0.0",
|
17
17
|
"pandas>=2.0.0",
|
18
|
-
"numpy>=1.20.0"
|
18
|
+
"numpy>=1.20.0",
|
19
|
+
"duckdb>=1.2.0"
|
19
20
|
]
|
20
21
|
|
21
22
|
[project.scripts]
|
@@ -0,0 +1,31 @@
|
|
1
|
+
"""
|
2
|
+
parqv - A Textual application for visualizing Parquet and JSON files.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from .app import ParqV
|
6
|
+
from .cli import run_app
|
7
|
+
from .core import (
|
8
|
+
SUPPORTED_EXTENSIONS,
|
9
|
+
DEFAULT_PREVIEW_ROWS,
|
10
|
+
FileValidationError,
|
11
|
+
validate_and_detect_file,
|
12
|
+
HandlerFactory,
|
13
|
+
HandlerCreationError,
|
14
|
+
setup_logging,
|
15
|
+
get_logger
|
16
|
+
)
|
17
|
+
|
18
|
+
__version__ = "1.0.0"
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
"ParqV",
|
22
|
+
"run_app",
|
23
|
+
"SUPPORTED_EXTENSIONS",
|
24
|
+
"DEFAULT_PREVIEW_ROWS",
|
25
|
+
"FileValidationError",
|
26
|
+
"validate_and_detect_file",
|
27
|
+
"HandlerFactory",
|
28
|
+
"HandlerCreationError",
|
29
|
+
"setup_logging",
|
30
|
+
"get_logger",
|
31
|
+
]
|
@@ -0,0 +1,150 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from textual.app import App, ComposeResult, Binding
|
5
|
+
from textual.containers import Container
|
6
|
+
from textual.widgets import Header, Footer, Static, Label, TabbedContent, TabPane
|
7
|
+
|
8
|
+
from .core import CSS_PATH, FileValidationError, validate_and_detect_file, HandlerFactory, HandlerCreationError, get_logger
|
9
|
+
from .data_sources import DataHandler
|
10
|
+
from .views.data_view import DataView
|
11
|
+
from .views.metadata_view import MetadataView
|
12
|
+
from .views.schema_view import SchemaView
|
13
|
+
|
14
|
+
log = get_logger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
class ParqV(App[None]):
|
18
|
+
"""A Textual app to visualize Parquet or JSON files."""
|
19
|
+
|
20
|
+
CSS_PATH = CSS_PATH
|
21
|
+
BINDINGS = [
|
22
|
+
Binding("q", "quit", "Quit", priority=True),
|
23
|
+
]
|
24
|
+
|
25
|
+
def __init__(self, file_path_str: Optional[str] = None, *args, **kwargs):
|
26
|
+
"""
|
27
|
+
Initialize the ParqV application.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
file_path_str: Path to the file to visualize
|
31
|
+
*args, **kwargs: Additional arguments for the Textual App
|
32
|
+
"""
|
33
|
+
super().__init__(*args, **kwargs)
|
34
|
+
|
35
|
+
# Application state
|
36
|
+
self.file_path: Optional[Path] = None
|
37
|
+
self.handler: Optional[DataHandler] = None
|
38
|
+
self.handler_type: Optional[str] = None
|
39
|
+
self.error_message: Optional[str] = None
|
40
|
+
|
41
|
+
# Initialize with file if provided
|
42
|
+
if file_path_str:
|
43
|
+
self._initialize_file_handler(file_path_str)
|
44
|
+
|
45
|
+
def _initialize_file_handler(self, file_path_str: str) -> None:
|
46
|
+
"""
|
47
|
+
Initialize the file handler for the given file path.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
file_path_str: Path to the file to process
|
51
|
+
"""
|
52
|
+
try:
|
53
|
+
# Validate file and detect type
|
54
|
+
self.file_path, self.handler_type = validate_and_detect_file(file_path_str)
|
55
|
+
|
56
|
+
# Create appropriate handler
|
57
|
+
self.handler = HandlerFactory.create_handler(self.file_path, self.handler_type)
|
58
|
+
|
59
|
+
log.info(f"Successfully initialized {self.handler_type} handler for: {self.file_path.name}")
|
60
|
+
|
61
|
+
except (FileValidationError, HandlerCreationError) as e:
|
62
|
+
self.error_message = str(e)
|
63
|
+
log.error(f"Failed to initialize handler: {e}")
|
64
|
+
|
65
|
+
except Exception as e:
|
66
|
+
self.error_message = f"An unexpected error occurred: {e}"
|
67
|
+
log.exception("Unexpected error during handler initialization")
|
68
|
+
|
69
|
+
def compose(self) -> ComposeResult:
|
70
|
+
"""Compose the UI layout."""
|
71
|
+
yield Header()
|
72
|
+
|
73
|
+
if self.error_message:
|
74
|
+
log.debug(f"Displaying error message: {self.error_message}")
|
75
|
+
yield Container(
|
76
|
+
Label("Error Loading File:", classes="error-title"),
|
77
|
+
Static(self.error_message, classes="error-content"),
|
78
|
+
id="error-container"
|
79
|
+
)
|
80
|
+
elif self.handler:
|
81
|
+
log.debug(f"Composing main layout with TabbedContent for {self.handler_type} handler.")
|
82
|
+
with TabbedContent(id="main-tabs"):
|
83
|
+
yield TabPane("Metadata", MetadataView(id="metadata-view"), id="tab-metadata")
|
84
|
+
yield TabPane("Schema", SchemaView(id="schema-view"), id="tab-schema")
|
85
|
+
yield TabPane("Data Preview", DataView(id="data-view"), id="tab-data")
|
86
|
+
else:
|
87
|
+
log.warning("No handler available and no error message set")
|
88
|
+
yield Container(
|
89
|
+
Label("No file loaded.", classes="error-title"),
|
90
|
+
Static("Please provide a valid file path.", classes="error-content"),
|
91
|
+
id="no-file-container"
|
92
|
+
)
|
93
|
+
|
94
|
+
yield Footer()
|
95
|
+
|
96
|
+
def on_mount(self) -> None:
|
97
|
+
"""Handle app mount event - set up header information."""
|
98
|
+
log.debug("App mounted.")
|
99
|
+
self._update_header()
|
100
|
+
|
101
|
+
def _update_header(self) -> None:
|
102
|
+
"""Update the header with file and format information."""
|
103
|
+
try:
|
104
|
+
header = self.query_one(Header)
|
105
|
+
|
106
|
+
if self.handler and self.file_path and self.handler_type:
|
107
|
+
display_name = self.file_path.name
|
108
|
+
format_name = self.handler_type.capitalize()
|
109
|
+
header.title = f"parqv - {display_name}"
|
110
|
+
header.sub_title = f"Format: {format_name}"
|
111
|
+
elif self.error_message:
|
112
|
+
header.title = "parqv - Error"
|
113
|
+
header.sub_title = "Failed to load file"
|
114
|
+
else:
|
115
|
+
header.title = "parqv"
|
116
|
+
header.sub_title = "File Viewer"
|
117
|
+
|
118
|
+
except Exception as e:
|
119
|
+
log.error(f"Failed to update header: {e}")
|
120
|
+
|
121
|
+
def action_quit(self) -> None:
|
122
|
+
"""Handle quit action - cleanup and exit."""
|
123
|
+
log.info("Quit action triggered.")
|
124
|
+
self._cleanup()
|
125
|
+
self.exit()
|
126
|
+
|
127
|
+
def _cleanup(self) -> None:
|
128
|
+
"""Clean up resources before exit."""
|
129
|
+
if self.handler:
|
130
|
+
try:
|
131
|
+
self.handler.close()
|
132
|
+
log.info("Handler closed successfully.")
|
133
|
+
except Exception as e:
|
134
|
+
log.error(f"Error during handler cleanup: {e}")
|
135
|
+
|
136
|
+
|
137
|
+
# For backward compatibility, keep the old CLI entry point
|
138
|
+
def run_app():
|
139
|
+
"""
|
140
|
+
Legacy CLI entry point for backward compatibility.
|
141
|
+
|
142
|
+
Note: New code should use parqv.cli.run_app() instead.
|
143
|
+
"""
|
144
|
+
from .cli import run_app as new_run_app
|
145
|
+
log.warning("Using legacy run_app(). Consider importing from parqv.cli instead.")
|
146
|
+
new_run_app()
|
147
|
+
|
148
|
+
|
149
|
+
if __name__ == "__main__":
|
150
|
+
run_app()
|
@@ -0,0 +1,112 @@
|
|
1
|
+
"""
|
2
|
+
Command Line Interface for parqv application.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import sys
|
6
|
+
|
7
|
+
from .app import ParqV
|
8
|
+
from .core import SUPPORTED_EXTENSIONS, FileValidationError, validate_and_detect_file, setup_logging, get_logger
|
9
|
+
|
10
|
+
|
11
|
+
def _print_user_message(message: str, log_level: str = "info") -> None:
|
12
|
+
"""
|
13
|
+
Show a message to the user and log it.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
message: message to display and log
|
17
|
+
log_level: log level ('info', 'error', 'warning')
|
18
|
+
"""
|
19
|
+
log = get_logger(__name__)
|
20
|
+
|
21
|
+
print(message, file=sys.stderr)
|
22
|
+
|
23
|
+
if log_level == "error":
|
24
|
+
log.error(message)
|
25
|
+
elif log_level == "warning":
|
26
|
+
log.warning(message)
|
27
|
+
else:
|
28
|
+
log.info(message)
|
29
|
+
|
30
|
+
|
31
|
+
def validate_cli_arguments() -> str:
|
32
|
+
"""
|
33
|
+
Validates command line arguments.
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
The file path string from command line arguments
|
37
|
+
|
38
|
+
Raises:
|
39
|
+
SystemExit: If arguments are invalid
|
40
|
+
"""
|
41
|
+
log = get_logger(__name__)
|
42
|
+
|
43
|
+
if len(sys.argv) < 2:
|
44
|
+
usage_message = "Usage: parqv <path_to_parquet_or_json_file>"
|
45
|
+
supported_message = f"Supported file types: {', '.join(SUPPORTED_EXTENSIONS.keys())}"
|
46
|
+
|
47
|
+
_print_user_message(usage_message, "error")
|
48
|
+
_print_user_message(supported_message, "info")
|
49
|
+
|
50
|
+
log.error("No file path provided via CLI arguments")
|
51
|
+
sys.exit(1)
|
52
|
+
|
53
|
+
file_path_str = sys.argv[1]
|
54
|
+
log.debug(f"File path received from CLI: {file_path_str}")
|
55
|
+
return file_path_str
|
56
|
+
|
57
|
+
|
58
|
+
def run_app() -> None:
|
59
|
+
"""
|
60
|
+
Main entry point for the parqv CLI application.
|
61
|
+
|
62
|
+
This function:
|
63
|
+
1. Sets up logging
|
64
|
+
2. Validates command line arguments
|
65
|
+
3. Validates the file path and type
|
66
|
+
4. Creates and runs the Textual app
|
67
|
+
"""
|
68
|
+
# Setup logging first
|
69
|
+
log = setup_logging()
|
70
|
+
log.info("--- parqv CLI started ---")
|
71
|
+
|
72
|
+
try:
|
73
|
+
# Get and validate CLI arguments
|
74
|
+
file_path_str = validate_cli_arguments()
|
75
|
+
|
76
|
+
# Validate file path and detect type (for early validation)
|
77
|
+
file_path, file_type = validate_and_detect_file(file_path_str)
|
78
|
+
log.info(f"File validated successfully: {file_path} (type: {file_type})")
|
79
|
+
|
80
|
+
# Create and run the app
|
81
|
+
log.info("Starting parqv application...")
|
82
|
+
app = ParqV(file_path_str=file_path_str)
|
83
|
+
app.run()
|
84
|
+
|
85
|
+
log.info("parqv application finished successfully")
|
86
|
+
|
87
|
+
except FileValidationError as e:
|
88
|
+
log.error(f"File validation failed: {e}")
|
89
|
+
|
90
|
+
error_message = f"Error: {e}"
|
91
|
+
help_message = f"Please provide a file with one of these extensions: {', '.join(SUPPORTED_EXTENSIONS.keys())}"
|
92
|
+
|
93
|
+
_print_user_message(error_message, "error")
|
94
|
+
_print_user_message(help_message, "info")
|
95
|
+
|
96
|
+
log.error("Exiting due to file validation error")
|
97
|
+
sys.exit(1)
|
98
|
+
|
99
|
+
except KeyboardInterrupt:
|
100
|
+
log.info("Application interrupted by user (Ctrl+C)")
|
101
|
+
_print_user_message("\nApplication interrupted by user.", "info")
|
102
|
+
sys.exit(0)
|
103
|
+
|
104
|
+
except Exception as e:
|
105
|
+
log.exception(f"Unexpected error in CLI: {e}")
|
106
|
+
_print_user_message(f"An unexpected error occurred: {e}", "error")
|
107
|
+
_print_user_message("Check the log file for more details.", "info")
|
108
|
+
sys.exit(1)
|
109
|
+
|
110
|
+
|
111
|
+
if __name__ == "__main__":
|
112
|
+
run_app()
|
@@ -0,0 +1,31 @@
|
|
1
|
+
"""
|
2
|
+
Core modules for parqv application.
|
3
|
+
|
4
|
+
This package contains fundamental configuration, utilities, and factory classes.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .config import SUPPORTED_EXTENSIONS, DEFAULT_PREVIEW_ROWS, CSS_PATH
|
8
|
+
from .logging import setup_logging, get_logger
|
9
|
+
from .file_utils import FileValidationError, validate_and_detect_file, validate_file_path, detect_file_type
|
10
|
+
from .handler_factory import HandlerFactory, HandlerCreationError
|
11
|
+
|
12
|
+
__all__ = [
|
13
|
+
# Configuration
|
14
|
+
"SUPPORTED_EXTENSIONS",
|
15
|
+
"DEFAULT_PREVIEW_ROWS",
|
16
|
+
"CSS_PATH",
|
17
|
+
|
18
|
+
# Logging
|
19
|
+
"setup_logging",
|
20
|
+
"get_logger",
|
21
|
+
|
22
|
+
# File utilities
|
23
|
+
"FileValidationError",
|
24
|
+
"validate_and_detect_file",
|
25
|
+
"validate_file_path",
|
26
|
+
"detect_file_type",
|
27
|
+
|
28
|
+
# Factory
|
29
|
+
"HandlerFactory",
|
30
|
+
"HandlerCreationError",
|
31
|
+
]
|
@@ -0,0 +1,25 @@
|
|
1
|
+
"""
|
2
|
+
Configuration constants and settings for parqv application.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Type, List
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
# File extensions and their corresponding handler types
|
9
|
+
SUPPORTED_EXTENSIONS: Dict[str, str] = {
|
10
|
+
".parquet": "parquet",
|
11
|
+
".json": "json",
|
12
|
+
".ndjson": "json"
|
13
|
+
}
|
14
|
+
|
15
|
+
# Application constants
|
16
|
+
LOG_FILENAME = "parqv.log"
|
17
|
+
LOG_MAX_BYTES = 1024 * 1024 * 5 # 5MB
|
18
|
+
LOG_BACKUP_COUNT = 3
|
19
|
+
LOG_ENCODING = "utf-8"
|
20
|
+
|
21
|
+
# UI Constants
|
22
|
+
DEFAULT_PREVIEW_ROWS = 50
|
23
|
+
|
24
|
+
# CSS Path (relative to the app module)
|
25
|
+
CSS_PATH = "parqv.css"
|
@@ -0,0 +1,88 @@
|
|
1
|
+
"""
|
2
|
+
File utilities for parqv application.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Optional, Tuple
|
7
|
+
|
8
|
+
from .config import SUPPORTED_EXTENSIONS
|
9
|
+
from .logging import get_logger
|
10
|
+
|
11
|
+
log = get_logger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
class FileValidationError(Exception):
|
15
|
+
"""Exception raised when file validation fails."""
|
16
|
+
pass
|
17
|
+
|
18
|
+
|
19
|
+
def validate_file_path(file_path_str: Optional[str]) -> Path:
|
20
|
+
"""
|
21
|
+
Validates and resolves the file path.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
file_path_str: String representation of the file path
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
Resolved Path object
|
28
|
+
|
29
|
+
Raises:
|
30
|
+
FileValidationError: If file path is invalid or file doesn't exist
|
31
|
+
"""
|
32
|
+
if not file_path_str:
|
33
|
+
raise FileValidationError("No file path provided.")
|
34
|
+
|
35
|
+
file_path = Path(file_path_str)
|
36
|
+
log.debug(f"Validating file path: {file_path}")
|
37
|
+
|
38
|
+
if not file_path.is_file():
|
39
|
+
raise FileValidationError(f"File not found or is not a regular file: {file_path}")
|
40
|
+
|
41
|
+
return file_path
|
42
|
+
|
43
|
+
|
44
|
+
def detect_file_type(file_path: Path) -> str:
|
45
|
+
"""
|
46
|
+
Detects the file type based on its extension.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
file_path: Path object representing the file
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
String representing the detected file type ('parquet' or 'json')
|
53
|
+
|
54
|
+
Raises:
|
55
|
+
FileValidationError: If file extension is not supported
|
56
|
+
"""
|
57
|
+
file_suffix = file_path.suffix.lower()
|
58
|
+
|
59
|
+
if file_suffix not in SUPPORTED_EXTENSIONS:
|
60
|
+
supported_exts = ", ".join(SUPPORTED_EXTENSIONS.keys())
|
61
|
+
raise FileValidationError(
|
62
|
+
f"Unsupported file extension: '{file_suffix}'. "
|
63
|
+
f"Only {supported_exts} are supported."
|
64
|
+
)
|
65
|
+
|
66
|
+
detected_type = SUPPORTED_EXTENSIONS[file_suffix]
|
67
|
+
log.info(f"Detected '{file_suffix}' extension, type: {detected_type}")
|
68
|
+
|
69
|
+
return detected_type
|
70
|
+
|
71
|
+
|
72
|
+
def validate_and_detect_file(file_path_str: Optional[str]) -> Tuple[Path, str]:
|
73
|
+
"""
|
74
|
+
Convenience function that validates file path and detects file type.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
file_path_str: String representation of the file path
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
Tuple of (validated_path, detected_type)
|
81
|
+
|
82
|
+
Raises:
|
83
|
+
FileValidationError: If validation or type detection fails
|
84
|
+
"""
|
85
|
+
file_path = validate_file_path(file_path_str)
|
86
|
+
file_type = detect_file_type(file_path)
|
87
|
+
|
88
|
+
return file_path, file_type
|