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.
@@ -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