dataframe-textual 1.1.5__py3-none-any.whl → 1.3.9__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/common.py +33 -12
- dataframe_textual/data_frame_help_panel.py +6 -4
- dataframe_textual/data_frame_table.py +574 -309
- dataframe_textual/data_frame_viewer.py +19 -27
- dataframe_textual/sql_screen.py +202 -0
- dataframe_textual/table_screen.py +20 -18
- dataframe_textual/yes_no_screen.py +9 -5
- {dataframe_textual-1.1.5.dist-info → dataframe_textual-1.3.9.dist-info}/METADATA +88 -19
- dataframe_textual-1.3.9.dist-info/RECORD +14 -0
- {dataframe_textual-1.1.5.dist-info → dataframe_textual-1.3.9.dist-info}/entry_points.txt +1 -0
- dataframe_textual-1.1.5.dist-info/RECORD +0 -13
- {dataframe_textual-1.1.5.dist-info → dataframe_textual-1.3.9.dist-info}/WHEEL +0 -0
- {dataframe_textual-1.1.5.dist-info → dataframe_textual-1.3.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -10,7 +10,7 @@ from textual.app import App, ComposeResult
|
|
|
10
10
|
from textual.css.query import NoMatches
|
|
11
11
|
from textual.theme import BUILTIN_THEMES
|
|
12
12
|
from textual.widgets import TabbedContent, TabPane
|
|
13
|
-
from textual.widgets.tabbed_content import
|
|
13
|
+
from textual.widgets.tabbed_content import ContentTabs
|
|
14
14
|
|
|
15
15
|
from .common import get_next_item, load_file
|
|
16
16
|
from .data_frame_help_panel import DataFrameHelpPanel
|
|
@@ -26,7 +26,7 @@ class DataFrameViewer(App):
|
|
|
26
26
|
|
|
27
27
|
## 🎯 File & Tab Management
|
|
28
28
|
- **Ctrl+O** - 📁 Add a new tab
|
|
29
|
-
- **Ctrl+
|
|
29
|
+
- **Ctrl+A** - 💾 Save all tabs
|
|
30
30
|
- **Ctrl+W** - ❌ Close current tab
|
|
31
31
|
- **>** or **b** - ▶️ Next tab
|
|
32
32
|
- **<** - ◀️ Previous tab
|
|
@@ -34,7 +34,7 @@ class DataFrameViewer(App):
|
|
|
34
34
|
- **q** - 🚪 Quit application
|
|
35
35
|
|
|
36
36
|
## 🎨 View & Settings
|
|
37
|
-
- **
|
|
37
|
+
- **F1** - ❓ Toggle this help panel
|
|
38
38
|
- **k** - 🌙 Cycle through themes
|
|
39
39
|
|
|
40
40
|
## ⭐ Features
|
|
@@ -51,30 +51,25 @@ class DataFrameViewer(App):
|
|
|
51
51
|
|
|
52
52
|
BINDINGS = [
|
|
53
53
|
("q", "quit", "Quit"),
|
|
54
|
-
("
|
|
54
|
+
("f1", "toggle_help_panel", "Help"),
|
|
55
55
|
("B", "toggle_tab_bar", "Toggle Tab Bar"),
|
|
56
56
|
("ctrl+o", "add_tab", "Add Tab"),
|
|
57
|
-
("ctrl+
|
|
57
|
+
("ctrl+a", "save_all_tabs", "Save All Tabs"),
|
|
58
58
|
("ctrl+w", "close_tab", "Close Tab"),
|
|
59
59
|
("greater_than_sign,b", "next_tab(1)", "Next Tab"),
|
|
60
60
|
("less_than_sign", "next_tab(-1)", "Prev Tab"),
|
|
61
61
|
]
|
|
62
62
|
|
|
63
63
|
CSS = """
|
|
64
|
-
TabbedContent {
|
|
65
|
-
height: 100%; /* Or a specific value, e.g., 20; */
|
|
66
|
-
}
|
|
67
64
|
TabbedContent > ContentTabs {
|
|
68
65
|
dock: bottom;
|
|
69
66
|
}
|
|
70
67
|
TabbedContent > ContentSwitcher {
|
|
71
68
|
overflow: auto;
|
|
72
|
-
height: 1fr;
|
|
69
|
+
height: 1fr;
|
|
73
70
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
background: $primary;
|
|
77
|
-
color: $text;
|
|
71
|
+
ContentTab.-active {
|
|
72
|
+
background: $block-cursor-background; /* Same as underline */
|
|
78
73
|
}
|
|
79
74
|
"""
|
|
80
75
|
|
|
@@ -178,12 +173,6 @@ class DataFrameViewer(App):
|
|
|
178
173
|
if table.loaded_rows == 0:
|
|
179
174
|
table._setup_table()
|
|
180
175
|
|
|
181
|
-
# Apply background color to active tab
|
|
182
|
-
event.tab.add_class("active")
|
|
183
|
-
for tab in self.tabbed.query(ContentTab):
|
|
184
|
-
if tab != event.tab:
|
|
185
|
-
tab.remove_class("active")
|
|
186
|
-
|
|
187
176
|
def action_toggle_help_panel(self) -> None:
|
|
188
177
|
"""Toggle the help panel on or off.
|
|
189
178
|
|
|
@@ -305,15 +294,15 @@ class DataFrameViewer(App):
|
|
|
305
294
|
try:
|
|
306
295
|
n_tab = 0
|
|
307
296
|
for lf, filename, tabname in load_file(filename, prefix_sheet=True):
|
|
308
|
-
self._add_tab(lf
|
|
297
|
+
self._add_tab(lf, filename, tabname)
|
|
309
298
|
n_tab += 1
|
|
310
|
-
self.notify(f"Added [$accent]{n_tab}[/] tab(s) for [$success]{filename}[/]", title="Open")
|
|
299
|
+
# self.notify(f"Added [$accent]{n_tab}[/] tab(s) for [$success]{filename}[/]", title="Open")
|
|
311
300
|
except Exception as e:
|
|
312
|
-
self.notify(f"Error: {e}", title="Open", severity="error")
|
|
301
|
+
self.notify(f"Error loading [$error]{filename}[/]: {str(e)}", title="Open", severity="error")
|
|
313
302
|
else:
|
|
314
303
|
self.notify(f"File does not exist: [$warning]{filename}[/]", title="Open", severity="warning")
|
|
315
304
|
|
|
316
|
-
def _add_tab(self, df: pl.DataFrame, filename: str, tabname: str) -> None:
|
|
305
|
+
def _add_tab(self, df: pl.DataFrame | pl.LazyFrame, filename: str, tabname: str) -> None:
|
|
317
306
|
"""Add new tab for the given DataFrame.
|
|
318
307
|
|
|
319
308
|
Creates and adds a new tab with the provided DataFrame and configuration.
|
|
@@ -321,15 +310,18 @@ class DataFrameViewer(App):
|
|
|
321
310
|
if this is no longer the only tab.
|
|
322
311
|
|
|
323
312
|
Args:
|
|
324
|
-
|
|
313
|
+
lf: The Polars DataFrame to display in the new tab.
|
|
325
314
|
filename: The source filename for this data (used in table metadata).
|
|
326
315
|
tabname: The display name for the tab.
|
|
327
316
|
|
|
328
317
|
Returns:
|
|
329
318
|
None
|
|
330
319
|
"""
|
|
331
|
-
|
|
332
|
-
|
|
320
|
+
# Ensure unique tab names
|
|
321
|
+
counter = 1
|
|
322
|
+
while any(tab.name == tabname for tab in self.tabs):
|
|
323
|
+
tabname = f"{tabname}_{counter}"
|
|
324
|
+
counter += 1
|
|
333
325
|
|
|
334
326
|
# Find an available tab index
|
|
335
327
|
tab_idx = f"tab_{len(self.tabs) + 1}"
|
|
@@ -369,6 +361,6 @@ class DataFrameViewer(App):
|
|
|
369
361
|
if active_pane := self.tabbed.active_pane:
|
|
370
362
|
self.tabbed.remove_pane(active_pane.id)
|
|
371
363
|
self.tabs.pop(active_pane)
|
|
372
|
-
self.notify(f"Closed tab [$success]{active_pane.name}[/]", title="Close")
|
|
364
|
+
# self.notify(f"Closed tab [$success]{active_pane.name}[/]", title="Close")
|
|
373
365
|
except NoMatches:
|
|
374
366
|
pass
|
|
@@ -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()
|
|
@@ -30,12 +30,14 @@ class TableScreen(ModalScreen):
|
|
|
30
30
|
|
|
31
31
|
TableScreen > DataTable {
|
|
32
32
|
width: auto;
|
|
33
|
-
|
|
33
|
+
height: auto;
|
|
34
34
|
border: solid $primary;
|
|
35
|
+
max-width: 100%;
|
|
36
|
+
overflow: auto;
|
|
35
37
|
}
|
|
36
38
|
"""
|
|
37
39
|
|
|
38
|
-
def __init__(self, dftable: DataFrameTable) -> None:
|
|
40
|
+
def __init__(self, dftable: "DataFrameTable") -> None:
|
|
39
41
|
"""Initialize the table screen.
|
|
40
42
|
|
|
41
43
|
Sets up the base modal screen with reference to the main DataFrameTable widget
|
|
@@ -48,8 +50,8 @@ class TableScreen(ModalScreen):
|
|
|
48
50
|
None
|
|
49
51
|
"""
|
|
50
52
|
super().__init__()
|
|
51
|
-
self.df: pl.DataFrame = dftable.df # Polars DataFrame
|
|
52
53
|
self.dftable = dftable # DataFrameTable
|
|
54
|
+
self.df: pl.DataFrame = dftable.df # Polars DataFrame
|
|
53
55
|
self.thousand_separator = False # Whether to use thousand separators in numbers
|
|
54
56
|
|
|
55
57
|
def compose(self) -> ComposeResult:
|
|
@@ -223,7 +225,7 @@ class StatisticsScreen(TableScreen):
|
|
|
223
225
|
|
|
224
226
|
CSS = TableScreen.DEFAULT_CSS.replace("TableScreen", "StatisticsScreen")
|
|
225
227
|
|
|
226
|
-
def __init__(self, dftable: DataFrameTable, col_idx: int | None = None):
|
|
228
|
+
def __init__(self, dftable: "DataFrameTable", col_idx: int | None = None):
|
|
227
229
|
super().__init__(dftable)
|
|
228
230
|
self.col_idx = col_idx # None for dataframe statistics, otherwise column index
|
|
229
231
|
|
|
@@ -291,6 +293,10 @@ class StatisticsScreen(TableScreen):
|
|
|
291
293
|
if False in self.dftable.visible_rows:
|
|
292
294
|
lf = lf.filter(self.dftable.visible_rows)
|
|
293
295
|
|
|
296
|
+
# Apply only to non-hidden columns
|
|
297
|
+
if self.dftable.hidden_columns:
|
|
298
|
+
lf = lf.select(pl.exclude(self.dftable.hidden_columns))
|
|
299
|
+
|
|
294
300
|
# Get dataframe statistics
|
|
295
301
|
stats_df = lf.collect().describe()
|
|
296
302
|
|
|
@@ -338,15 +344,16 @@ class FrequencyScreen(TableScreen):
|
|
|
338
344
|
|
|
339
345
|
CSS = TableScreen.DEFAULT_CSS.replace("TableScreen", "FrequencyScreen")
|
|
340
346
|
|
|
341
|
-
def __init__(self, col_idx: int, dftable: DataFrameTable):
|
|
347
|
+
def __init__(self, col_idx: int, dftable: "DataFrameTable") -> None:
|
|
342
348
|
super().__init__(dftable)
|
|
343
349
|
self.col_idx = col_idx
|
|
344
350
|
self.sorted_columns = {
|
|
345
351
|
1: True, # Count
|
|
346
352
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
)
|
|
353
|
+
|
|
354
|
+
df = dftable.df.filter(dftable.visible_rows) if False in dftable.visible_rows else dftable.df
|
|
355
|
+
self.total_count = len(df)
|
|
356
|
+
self.df: pl.DataFrame = df[df.columns[self.col_idx]].value_counts(sort=True).sort("count", descending=True)
|
|
350
357
|
|
|
351
358
|
def on_mount(self) -> None:
|
|
352
359
|
"""Create the frequency table."""
|
|
@@ -379,9 +386,6 @@ class FrequencyScreen(TableScreen):
|
|
|
379
386
|
dtype = self.dftable.df.dtypes[self.col_idx]
|
|
380
387
|
dc = DtypeConfig(dtype)
|
|
381
388
|
|
|
382
|
-
# Calculate frequencies using Polars
|
|
383
|
-
total_count = len(self.dftable.df)
|
|
384
|
-
|
|
385
389
|
# Add column headers with sort indicators
|
|
386
390
|
columns = [
|
|
387
391
|
(column, "Value", 0),
|
|
@@ -409,7 +413,7 @@ class FrequencyScreen(TableScreen):
|
|
|
409
413
|
# Add rows to the frequency table
|
|
410
414
|
for row_idx, row in enumerate(self.df.rows()):
|
|
411
415
|
column, count = row
|
|
412
|
-
percentage = (count / total_count) * 100
|
|
416
|
+
percentage = (count / self.total_count) * 100
|
|
413
417
|
|
|
414
418
|
if column is None:
|
|
415
419
|
value = NULL_DISPLAY
|
|
@@ -440,7 +444,7 @@ class FrequencyScreen(TableScreen):
|
|
|
440
444
|
# Add a total row
|
|
441
445
|
self.table.add_row(
|
|
442
446
|
Text("Total", style="bold", justify=dc.justify),
|
|
443
|
-
Text(f"{total_count:,}", style="bold", justify="right"),
|
|
447
|
+
Text(f"{self.total_count:,}", style="bold", justify="right"),
|
|
444
448
|
Text("100.00", style="bold", justify="right"),
|
|
445
449
|
Bar(
|
|
446
450
|
highlight_range=(0.0, 10),
|
|
@@ -454,12 +458,10 @@ class FrequencyScreen(TableScreen):
|
|
|
454
458
|
row_idx, col_idx = self.table.cursor_coordinate
|
|
455
459
|
col_sort = col_idx if col_idx == 0 else 1
|
|
456
460
|
|
|
457
|
-
|
|
458
|
-
if sort_dir is not None:
|
|
461
|
+
if self.sorted_columns.get(col_sort) == descending:
|
|
459
462
|
# If already sorted in the same direction, do nothing
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return
|
|
463
|
+
# self.notify("Already sorted in that order", title="Sort", severity="warning")
|
|
464
|
+
return
|
|
463
465
|
|
|
464
466
|
self.sorted_columns.clear()
|
|
465
467
|
self.sorted_columns[col_sort] = descending
|
|
@@ -33,9 +33,11 @@ class YesNoScreen(ModalScreen):
|
|
|
33
33
|
min-width: 40;
|
|
34
34
|
max-width: 60;
|
|
35
35
|
height: auto;
|
|
36
|
-
border: heavy $
|
|
37
|
-
border-title-color: $
|
|
38
|
-
background: $
|
|
36
|
+
border: heavy $accent;
|
|
37
|
+
border-title-color: $accent;
|
|
38
|
+
border-title-background: $panel;
|
|
39
|
+
border-title-style: bold;
|
|
40
|
+
background: $background;
|
|
39
41
|
padding: 1 2;
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -241,6 +243,7 @@ class YesNoScreen(ModalScreen):
|
|
|
241
243
|
yield self.no
|
|
242
244
|
|
|
243
245
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
246
|
+
"""Handle button press events in the Yes/No screen."""
|
|
244
247
|
if event.button.id == "yes":
|
|
245
248
|
self._handle_yes()
|
|
246
249
|
elif event.button.id == "maybe":
|
|
@@ -249,6 +252,7 @@ class YesNoScreen(ModalScreen):
|
|
|
249
252
|
self.dismiss(None)
|
|
250
253
|
|
|
251
254
|
def on_key(self, event) -> None:
|
|
255
|
+
"""Handle key press events in the table screen."""
|
|
252
256
|
if event.key == "enter":
|
|
253
257
|
self._handle_yes()
|
|
254
258
|
event.stop()
|
|
@@ -609,7 +613,7 @@ class AddColumnScreen(YesNoScreen):
|
|
|
609
613
|
return self.cidx, col_name, pl.lit(None)
|
|
610
614
|
elif tentative_expr(term):
|
|
611
615
|
try:
|
|
612
|
-
expr = validate_expr(term, self.df, self.cidx)
|
|
616
|
+
expr = validate_expr(term, self.df.columns, self.cidx)
|
|
613
617
|
return self.cidx, col_name, expr
|
|
614
618
|
except ValueError as e:
|
|
615
619
|
self.notify(f"Invalid expression [$error]{term}[/]: {str(e)}", title="Add Column", severity="error")
|
|
@@ -634,7 +638,7 @@ class FindReplaceScreen(YesNoScreen):
|
|
|
634
638
|
|
|
635
639
|
CSS = YesNoScreen.DEFAULT_CSS.replace("YesNoScreen", "ReplaceScreen")
|
|
636
640
|
|
|
637
|
-
def __init__(self, dftable: DataFrameTable):
|
|
641
|
+
def __init__(self, dftable: "DataFrameTable"):
|
|
638
642
|
term_find = str(dftable.cursor_value)
|
|
639
643
|
super().__init__(
|
|
640
644
|
title="Find and Replace",
|