dataframe-textual 1.5.0__py3-none-any.whl → 1.9.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.
@@ -1,5 +1,6 @@
1
1
  """Modal screens for Polars sql manipulation"""
2
2
 
3
+ from functools import partial
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  if TYPE_CHECKING:
@@ -46,18 +47,20 @@ class SqlScreen(ModalScreen):
46
47
 
47
48
  """
48
49
 
49
- def __init__(self, dftable: "DataFrameTable", on_yes_callback=None) -> None:
50
+ def __init__(self, dftable: "DataFrameTable", on_yes_callback=None, on_maybe_callback=None) -> None:
50
51
  """Initialize the SQL screen."""
51
52
  super().__init__()
52
53
  self.dftable = dftable # DataFrameTable
53
54
  self.df: pl.DataFrame = dftable.df # Polars DataFrame
54
55
  self.on_yes_callback = on_yes_callback
56
+ self.on_maybe_callback = on_maybe_callback
55
57
 
56
58
  def compose(self) -> ComposeResult:
57
59
  """Compose the SQL screen widget structure."""
58
60
  # Shared by subclasses
59
61
  with Horizontal(id="button-container"):
60
- yield Button("Apply", id="yes", variant="success")
62
+ yield Button("View", id="yes", variant="success")
63
+ yield Button("Filter", id="maybe", variant="warning")
61
64
  yield Button("Cancel", id="no", variant="error")
62
65
 
63
66
  def on_key(self, event) -> None:
@@ -66,7 +69,16 @@ class SqlScreen(ModalScreen):
66
69
  self.app.pop_screen()
67
70
  event.stop()
68
71
  elif event.key == "enter":
69
- self._handle_yes()
72
+ for button in self.query(Button):
73
+ if button.has_focus:
74
+ if button.id == "yes":
75
+ self._handle_yes()
76
+ elif button.id == "maybe":
77
+ self._handle_maybe()
78
+ break
79
+ else:
80
+ self._handle_yes()
81
+
70
82
  event.stop()
71
83
  elif event.key == "escape":
72
84
  self.dismiss(None)
@@ -76,6 +88,8 @@ class SqlScreen(ModalScreen):
76
88
  """Handle button press events in the SQL screen."""
77
89
  if event.button.id == "yes":
78
90
  self._handle_yes()
91
+ elif event.button.id == "maybe":
92
+ self._handle_maybe()
79
93
  elif event.button.id == "no":
80
94
  self.dismiss(None)
81
95
 
@@ -87,6 +101,14 @@ class SqlScreen(ModalScreen):
87
101
  else:
88
102
  self.dismiss(True)
89
103
 
104
+ def _handle_maybe(self) -> None:
105
+ """Handle Maybe button press."""
106
+ if self.on_maybe_callback:
107
+ result = self.on_maybe_callback()
108
+ self.dismiss(result)
109
+ else:
110
+ self.dismiss(True)
111
+
90
112
 
91
113
  class SimpleSqlScreen(SqlScreen):
92
114
  """Simple SQL query screen."""
@@ -133,25 +155,38 @@ class SimpleSqlScreen(SqlScreen):
133
155
  Returns:
134
156
  None
135
157
  """
136
- super().__init__(dftable, on_yes_callback=self._handle_simple)
158
+ super().__init__(
159
+ dftable,
160
+ on_yes_callback=self._handle_simple,
161
+ on_maybe_callback=partial(
162
+ self._handle_simple,
163
+ view=False,
164
+ ),
165
+ )
137
166
 
138
167
  def compose(self) -> ComposeResult:
139
168
  """Compose the simple SQL screen widget structure."""
140
169
  with Container(id="sql-container") as container:
141
170
  container.border_title = "SQL Query"
142
171
  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")
172
+ yield SelectionList(
173
+ *[Selection(col, col) for col in self.df.columns if col not in self.dftable.hidden_columns],
174
+ id="column-selection",
175
+ )
144
176
  yield Label("Where condition (optional)", id="where-label")
145
177
  yield Input(placeholder="e.g., age > 30 and height < 180", id="where-input")
146
178
  yield from super().compose()
147
179
 
148
- def _handle_simple(self) -> None:
180
+ def _handle_simple(self, view: bool = True) -> None:
149
181
  """Handle Yes button/Enter key press."""
150
182
  selections = self.query_one(SelectionList).selected
151
- columns = ", ".join(f"`{s}`" for s in selections) if selections else "*"
183
+ if not selections:
184
+ selections = [col for col in self.df.columns if col not in self.dftable.hidden_columns]
185
+
186
+ columns = ", ".join(f"`{s}`" for s in selections)
152
187
  where = self.query_one(Input).value.strip()
153
188
 
154
- return columns, where
189
+ return columns, where, view
155
190
 
156
191
 
157
192
  class AdvancedSqlScreen(SqlScreen):
@@ -184,7 +219,11 @@ class AdvancedSqlScreen(SqlScreen):
184
219
  Returns:
185
220
  None
186
221
  """
187
- super().__init__(dftable, on_yes_callback=self._handle_advanced)
222
+ super().__init__(
223
+ dftable,
224
+ on_yes_callback=self._handle_advanced,
225
+ on_maybe_callback=partial(self._handle_advanced, view=False),
226
+ )
188
227
 
189
228
  def compose(self) -> ComposeResult:
190
229
  """Compose the advanced SQL screen widget structure."""
@@ -197,6 +236,6 @@ class AdvancedSqlScreen(SqlScreen):
197
236
  )
198
237
  yield from super().compose()
199
238
 
200
- def _handle_advanced(self) -> None:
239
+ def _handle_advanced(self, view: bool = True) -> None:
201
240
  """Handle Yes button/Enter key press."""
202
- return self.query_one(TextArea).text.strip()
241
+ return self.query_one(TextArea).text.strip(), view
@@ -144,7 +144,7 @@ class TableScreen(ModalScreen):
144
144
  message = f"Highlighted [$accent]{col_name}[/] == [$success]{value_display}[/]"
145
145
 
146
146
  # Recreate the table display with updated data in the main app
147
- self.dftable._setup_table()
147
+ self.dftable.setup_table()
148
148
 
149
149
  # Dismiss the frequency screen
150
150
  self.app.pop_screen()
@@ -5,11 +5,13 @@ from typing import TYPE_CHECKING
5
5
  if TYPE_CHECKING:
6
6
  from .data_frame_table import DataFrameTable
7
7
 
8
+
8
9
  import polars as pl
9
10
  from textual.app import ComposeResult
10
11
  from textual.containers import Container, Horizontal
11
12
  from textual.screen import ModalScreen
12
- from textual.widgets import Button, Checkbox, Input, Label, Static
13
+ from textual.widgets import Button, Checkbox, Input, Label, Static, TabPane
14
+ from textual.widgets.tabbed_content import ContentTab
13
15
 
14
16
  from .common import NULL, DtypeConfig, tentative_expr, validate_expr
15
17
 
@@ -254,7 +256,18 @@ class YesNoScreen(ModalScreen):
254
256
  def on_key(self, event) -> None:
255
257
  """Handle key press events in the table screen."""
256
258
  if event.key == "enter":
257
- self._handle_yes()
259
+ for button in self.query(Button):
260
+ if button.has_focus:
261
+ if button.id == "yes":
262
+ self._handle_yes()
263
+ elif button.id == "maybe":
264
+ self._handle_maybe()
265
+ elif button.id == "no":
266
+ self.dismiss(None)
267
+ break
268
+ else:
269
+ self._handle_yes()
270
+
258
271
  event.stop()
259
272
  elif event.key == "escape":
260
273
  self.dismiss(None)
@@ -282,18 +295,26 @@ class SaveFileScreen(YesNoScreen):
282
295
 
283
296
  CSS = YesNoScreen.DEFAULT_CSS.replace("YesNoScreen", "SaveFileScreen")
284
297
 
285
- def __init__(self, filename: str, title="Save Tab"):
298
+ def __init__(
299
+ self, filename: str, title: str = "Save to File", all_tabs: bool | None = None, multi_tab: bool = False
300
+ ):
301
+ self.all_tabs = all_tabs or (all_tabs is None and multi_tab)
286
302
  super().__init__(
287
303
  title=title,
304
+ label="Enter filename",
288
305
  input=filename,
306
+ yes="Save",
307
+ maybe="Save All Tabs" if self.all_tabs else None,
308
+ no="Cancel",
289
309
  on_yes_callback=self.handle_save,
310
+ on_maybe_callback=self.handle_save,
290
311
  )
291
312
 
292
313
  def handle_save(self):
293
314
  if self.input:
294
315
  input_filename = self.input.value.strip()
295
316
  if input_filename:
296
- return input_filename
317
+ return input_filename, self.all_tabs
297
318
  else:
298
319
  self.notify("Filename cannot be empty", title="Save", severity="error")
299
320
  return None
@@ -587,7 +608,7 @@ class AddColumnScreen(YesNoScreen):
587
608
  title="Add Column",
588
609
  label="Enter column name",
589
610
  input="Link" if link else "Column name",
590
- label2="Enter link template, e.g., https://example.com/$1/id/$id, PC/compound/$_"
611
+ label2="Enter link template, e.g., https://example.com/$_/id/$1, PC/compound/$cid"
591
612
  if link
592
613
  else "Enter value or Polars expression, e.g., abc, pl.lit(123), NULL, $_ * 2, $1 + $total, $_.str.to_uppercase(), pl.concat_str($_, pl.lit('-suffix'))",
593
614
  input2="Link template" if link else "Column value or expression",
@@ -694,3 +715,43 @@ class FindReplaceScreen(YesNoScreen):
694
715
  replace_all = True
695
716
 
696
717
  return term_find, term_replace, match_nocase, match_whole, replace_all
718
+
719
+
720
+ class RenameTabScreen(YesNoScreen):
721
+ """Modal screen to rename a tab."""
722
+
723
+ CSS = YesNoScreen.DEFAULT_CSS.replace("YesNoScreen", "RenameTabScreen")
724
+
725
+ def __init__(self, content_tab: ContentTab, existing_tabs: list[TabPane]):
726
+ self.content_tab = content_tab
727
+ self.existing_tabs = existing_tabs
728
+ tab_name = content_tab.label_text
729
+
730
+ super().__init__(
731
+ title="Rename Tab",
732
+ label="New tab name",
733
+ input={"value": tab_name},
734
+ on_yes_callback=self._validate_input,
735
+ )
736
+
737
+ def _validate_input(self) -> None:
738
+ """Validate and save the new tab name."""
739
+ new_name = self.input.value.strip()
740
+
741
+ # Check if name is empty
742
+ if not new_name:
743
+ self.notify("Tab name cannot be empty", title="Rename Tab", severity="error")
744
+ return None
745
+
746
+ # Check if name changed
747
+ if new_name == self.content_tab.label_text:
748
+ self.notify("No changes made", title="Rename Tab", severity="warning")
749
+ return None
750
+
751
+ # Check if name already exists
752
+ if new_name in self.existing_tabs:
753
+ self.notify(f"Tab [$accent]{new_name}[/] already exists", title="Rename Tab", severity="error")
754
+ return None
755
+
756
+ # Return new name
757
+ return self.content_tab, new_name