dataframe-textual 0.3.2__py3-none-any.whl → 1.5.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.
- dataframe_textual/__init__.py +1 -2
- dataframe_textual/__main__.py +62 -14
- dataframe_textual/common.py +587 -92
- dataframe_textual/data_frame_help_panel.py +28 -8
- dataframe_textual/data_frame_table.py +2579 -704
- dataframe_textual/data_frame_viewer.py +215 -179
- dataframe_textual/sql_screen.py +202 -0
- dataframe_textual/table_screen.py +296 -100
- dataframe_textual/yes_no_screen.py +454 -165
- dataframe_textual-1.5.0.dist-info/METADATA +987 -0
- dataframe_textual-1.5.0.dist-info/RECORD +14 -0
- {dataframe_textual-0.3.2.dist-info → dataframe_textual-1.5.0.dist-info}/entry_points.txt +1 -0
- dataframe_textual-0.3.2.dist-info/METADATA +0 -548
- dataframe_textual-0.3.2.dist-info/RECORD +0 -13
- {dataframe_textual-0.3.2.dist-info → dataframe_textual-1.5.0.dist-info}/WHEEL +0 -0
- {dataframe_textual-0.3.2.dist-info → dataframe_textual-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Modal screens for Polars sql manipulation"""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .data_frame_table import DataFrameTable
|
|
7
|
+
|
|
8
|
+
import polars as pl
|
|
9
|
+
from textual.app import ComposeResult
|
|
10
|
+
from textual.containers import Container, Horizontal
|
|
11
|
+
from textual.screen import ModalScreen
|
|
12
|
+
from textual.widgets import Button, Input, Label, SelectionList, TextArea
|
|
13
|
+
from textual.widgets.selection_list import Selection
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SqlScreen(ModalScreen):
|
|
17
|
+
"""Base class for modal screens handling SQL query."""
|
|
18
|
+
|
|
19
|
+
DEFAULT_CSS = """
|
|
20
|
+
SqlScreen {
|
|
21
|
+
align: center middle;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
SqlScreen > Container {
|
|
25
|
+
width: auto;
|
|
26
|
+
height: auto;
|
|
27
|
+
border: heavy $accent;
|
|
28
|
+
border-title-color: $accent;
|
|
29
|
+
border-title-background: $panel;
|
|
30
|
+
border-title-style: bold;
|
|
31
|
+
background: $background;
|
|
32
|
+
padding: 1 2;
|
|
33
|
+
overflow: auto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#button-container {
|
|
37
|
+
width: auto;
|
|
38
|
+
margin: 1 0 0 0;
|
|
39
|
+
height: 3;
|
|
40
|
+
align: center middle;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Button {
|
|
44
|
+
margin: 0 2;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, dftable: "DataFrameTable", on_yes_callback=None) -> None:
|
|
50
|
+
"""Initialize the SQL screen."""
|
|
51
|
+
super().__init__()
|
|
52
|
+
self.dftable = dftable # DataFrameTable
|
|
53
|
+
self.df: pl.DataFrame = dftable.df # Polars DataFrame
|
|
54
|
+
self.on_yes_callback = on_yes_callback
|
|
55
|
+
|
|
56
|
+
def compose(self) -> ComposeResult:
|
|
57
|
+
"""Compose the SQL screen widget structure."""
|
|
58
|
+
# Shared by subclasses
|
|
59
|
+
with Horizontal(id="button-container"):
|
|
60
|
+
yield Button("Apply", id="yes", variant="success")
|
|
61
|
+
yield Button("Cancel", id="no", variant="error")
|
|
62
|
+
|
|
63
|
+
def on_key(self, event) -> None:
|
|
64
|
+
"""Handle key press events in the SQL screen"""
|
|
65
|
+
if event.key in ("q", "escape"):
|
|
66
|
+
self.app.pop_screen()
|
|
67
|
+
event.stop()
|
|
68
|
+
elif event.key == "enter":
|
|
69
|
+
self._handle_yes()
|
|
70
|
+
event.stop()
|
|
71
|
+
elif event.key == "escape":
|
|
72
|
+
self.dismiss(None)
|
|
73
|
+
event.stop()
|
|
74
|
+
|
|
75
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
76
|
+
"""Handle button press events in the SQL screen."""
|
|
77
|
+
if event.button.id == "yes":
|
|
78
|
+
self._handle_yes()
|
|
79
|
+
elif event.button.id == "no":
|
|
80
|
+
self.dismiss(None)
|
|
81
|
+
|
|
82
|
+
def _handle_yes(self) -> None:
|
|
83
|
+
"""Handle Yes button/Enter key press."""
|
|
84
|
+
if self.on_yes_callback:
|
|
85
|
+
result = self.on_yes_callback()
|
|
86
|
+
self.dismiss(result)
|
|
87
|
+
else:
|
|
88
|
+
self.dismiss(True)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class SimpleSqlScreen(SqlScreen):
|
|
92
|
+
"""Simple SQL query screen."""
|
|
93
|
+
|
|
94
|
+
DEFAULT_CSS = SqlScreen.DEFAULT_CSS.replace("SqlScreen", "SimpleSqlScreen")
|
|
95
|
+
|
|
96
|
+
CSS = """
|
|
97
|
+
SimpleSqlScreen SelectionList {
|
|
98
|
+
width: auto;
|
|
99
|
+
min-width: 40;
|
|
100
|
+
margin: 1 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
SimpleSqlScreen SelectionList:blur {
|
|
104
|
+
border: solid $secondary;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
SimpleSqlScreen Label {
|
|
108
|
+
width: auto;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
SimpleSqlScreen Input {
|
|
112
|
+
width: auto;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
SimpleSqlScreen Input:blur {
|
|
116
|
+
border: solid $secondary;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#button-container {
|
|
120
|
+
min-width: 40;
|
|
121
|
+
}
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, dftable: "DataFrameTable") -> None:
|
|
125
|
+
"""Initialize the simple SQL screen.
|
|
126
|
+
|
|
127
|
+
Sets up the modal screen with reference to the main DataFrameTable widget
|
|
128
|
+
and stores the DataFrame for display.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
dftable: Reference to the parent DataFrameTable widget.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
None
|
|
135
|
+
"""
|
|
136
|
+
super().__init__(dftable, on_yes_callback=self._handle_simple)
|
|
137
|
+
|
|
138
|
+
def compose(self) -> ComposeResult:
|
|
139
|
+
"""Compose the simple SQL screen widget structure."""
|
|
140
|
+
with Container(id="sql-container") as container:
|
|
141
|
+
container.border_title = "SQL Query"
|
|
142
|
+
yield Label("Select columns (default to all):", id="select-label")
|
|
143
|
+
yield SelectionList(*[Selection(col, col) for col in self.df.columns], id="column-selection")
|
|
144
|
+
yield Label("Where condition (optional)", id="where-label")
|
|
145
|
+
yield Input(placeholder="e.g., age > 30 and height < 180", id="where-input")
|
|
146
|
+
yield from super().compose()
|
|
147
|
+
|
|
148
|
+
def _handle_simple(self) -> None:
|
|
149
|
+
"""Handle Yes button/Enter key press."""
|
|
150
|
+
selections = self.query_one(SelectionList).selected
|
|
151
|
+
columns = ", ".join(f"`{s}`" for s in selections) if selections else "*"
|
|
152
|
+
where = self.query_one(Input).value.strip()
|
|
153
|
+
|
|
154
|
+
return columns, where
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class AdvancedSqlScreen(SqlScreen):
|
|
158
|
+
"""Advanced SQL query screen."""
|
|
159
|
+
|
|
160
|
+
DEFAULT_CSS = SqlScreen.DEFAULT_CSS.replace("SqlScreen", "AdvancedSqlScreen")
|
|
161
|
+
|
|
162
|
+
CSS = """
|
|
163
|
+
AdvancedSqlScreen TextArea {
|
|
164
|
+
width: auto;
|
|
165
|
+
min-width: 60;
|
|
166
|
+
height: auto;
|
|
167
|
+
min-height: 10;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#button-container {
|
|
171
|
+
min-width: 60;
|
|
172
|
+
}
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def __init__(self, dftable: "DataFrameTable") -> None:
|
|
176
|
+
"""Initialize the simple SQL screen.
|
|
177
|
+
|
|
178
|
+
Sets up the modal screen with reference to the main DataFrameTable widget
|
|
179
|
+
and stores the DataFrame for display.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
dftable: Reference to the parent DataFrameTable widget.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
None
|
|
186
|
+
"""
|
|
187
|
+
super().__init__(dftable, on_yes_callback=self._handle_advanced)
|
|
188
|
+
|
|
189
|
+
def compose(self) -> ComposeResult:
|
|
190
|
+
"""Compose the advanced SQL screen widget structure."""
|
|
191
|
+
with Container(id="sql-container") as container:
|
|
192
|
+
container.border_title = "Advanced SQL Query"
|
|
193
|
+
yield TextArea.code_editor(
|
|
194
|
+
placeholder="Enter SQL query (use `self` as the table name), e.g., \n\nSELECT * \nFROM self \nWHERE age > 30",
|
|
195
|
+
id="sql-textarea",
|
|
196
|
+
language="sql",
|
|
197
|
+
)
|
|
198
|
+
yield from super().compose()
|
|
199
|
+
|
|
200
|
+
def _handle_advanced(self) -> None:
|
|
201
|
+
"""Handle Yes button/Enter key press."""
|
|
202
|
+
return self.query_one(TextArea).text.strip()
|