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