dataframe-textual 2.7.0__tar.gz → 2.9.0__tar.gz

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.
Files changed (19) hide show
  1. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/PKG-INFO +11 -11
  2. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/README.md +10 -10
  3. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/pyproject.toml +1 -1
  4. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/src/dataframe_textual/data_frame_table.py +57 -43
  5. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/src/dataframe_textual/yes_no_screen.py +1 -7
  6. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/uv.lock +1 -1
  7. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/.gitignore +0 -0
  8. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/1811.csv.gz +0 -0
  9. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/LICENSE +0 -0
  10. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/glycosmos_glycans_list.csv.gz +0 -0
  11. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/large_malformed.tsv.gz +0 -0
  12. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/main.py +0 -0
  13. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/src/dataframe_textual/__init__.py +0 -0
  14. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/src/dataframe_textual/__main__.py +0 -0
  15. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/src/dataframe_textual/common.py +0 -0
  16. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/src/dataframe_textual/data_frame_help_panel.py +0 -0
  17. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/src/dataframe_textual/data_frame_viewer.py +0 -0
  18. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/src/dataframe_textual/sql_screen.py +0 -0
  19. {dataframe_textual-2.7.0 → dataframe_textual-2.9.0}/src/dataframe_textual/table_screen.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataframe-textual
3
- Version: 2.7.0
3
+ Version: 2.9.0
4
4
  Summary: Interactive terminal viewer/editor for tabular data
5
5
  Project-URL: Homepage, https://github.com/need47/dataframe-textual
6
6
  Project-URL: Repository, https://github.com/need47/dataframe-textual.git
@@ -310,7 +310,7 @@ zcat compressed_data.csv.gz | dv -f csv
310
310
  | `h` | Hide current column |
311
311
  | `H` | Show all hidden rows/columns |
312
312
 
313
- #### Data Editing
313
+ #### Editing
314
314
 
315
315
  | Key | Action |
316
316
  |-----|--------|
@@ -358,16 +358,10 @@ zcat compressed_data.csv.gz | dv -f csv
358
358
  | Key | Action |
359
359
  |-----|--------|
360
360
  | `"` (quote) | Filter selected rows (others removed) |
361
+ | `.` | View rows with non-null values in current column (others hidden) |
361
362
  | `v` | View selected rows (others hidden) |
362
363
  | `V` | View selected by expression (others hidden) |
363
364
 
364
- #### SQL Interface
365
-
366
- | Key | Action |
367
- |-----|--------|
368
- | `l` | Simple SQL interface (select columns & where clause) |
369
- | `L` | Advanced SQL interface (full SQL query with syntax highlight) |
370
-
371
365
  #### Sorting (supporting multiple columns)
372
366
 
373
367
  | Key | Action |
@@ -401,6 +395,13 @@ zcat compressed_data.csv.gz | dv -f csv
401
395
  | `Ctrl+C` | Copy column to clipboard |
402
396
  | `Ctrl+R` | Copy row to clipboard (tab-separated) |
403
397
 
398
+ #### SQL Interface
399
+
400
+ | Key | Action |
401
+ |-----|--------|
402
+ | `l` | Simple SQL interface (select columns & where clause) |
403
+ | `L` | Advanced SQL interface (full SQL query with syntax highlight) |
404
+
404
405
  ## Features in Detail
405
406
 
406
407
  ### 1. Color-Coded Data Types
@@ -447,7 +448,6 @@ These options work with plain text searches. Use Polars regex patterns in expres
447
448
  **Quick Tips:**
448
449
  - Search results highlight matching rows in **red**
449
450
  - Use expression for advanced selection (e.g., $attack > $defense)
450
- - Multiple searches **accumulate** - each new search adds to the selections or matches
451
451
  - Type-aware matching automatically converts values. Resort to string comparison if conversion fails
452
452
  - Use `u` to undo any search or filter
453
453
 
@@ -626,7 +626,7 @@ This is useful for:
626
626
  - Quick statistical summaries without external tools
627
627
  - Comparing statistics across columns
628
628
 
629
- ### 11. Data Editing
629
+ ### 11. Editing
630
630
 
631
631
  **Edit Cell** (`e` or **Double-click**):
632
632
  - Opens modal for editing current cell
@@ -271,7 +271,7 @@ zcat compressed_data.csv.gz | dv -f csv
271
271
  | `h` | Hide current column |
272
272
  | `H` | Show all hidden rows/columns |
273
273
 
274
- #### Data Editing
274
+ #### Editing
275
275
 
276
276
  | Key | Action |
277
277
  |-----|--------|
@@ -319,16 +319,10 @@ zcat compressed_data.csv.gz | dv -f csv
319
319
  | Key | Action |
320
320
  |-----|--------|
321
321
  | `"` (quote) | Filter selected rows (others removed) |
322
+ | `.` | View rows with non-null values in current column (others hidden) |
322
323
  | `v` | View selected rows (others hidden) |
323
324
  | `V` | View selected by expression (others hidden) |
324
325
 
325
- #### SQL Interface
326
-
327
- | Key | Action |
328
- |-----|--------|
329
- | `l` | Simple SQL interface (select columns & where clause) |
330
- | `L` | Advanced SQL interface (full SQL query with syntax highlight) |
331
-
332
326
  #### Sorting (supporting multiple columns)
333
327
 
334
328
  | Key | Action |
@@ -362,6 +356,13 @@ zcat compressed_data.csv.gz | dv -f csv
362
356
  | `Ctrl+C` | Copy column to clipboard |
363
357
  | `Ctrl+R` | Copy row to clipboard (tab-separated) |
364
358
 
359
+ #### SQL Interface
360
+
361
+ | Key | Action |
362
+ |-----|--------|
363
+ | `l` | Simple SQL interface (select columns & where clause) |
364
+ | `L` | Advanced SQL interface (full SQL query with syntax highlight) |
365
+
365
366
  ## Features in Detail
366
367
 
367
368
  ### 1. Color-Coded Data Types
@@ -408,7 +409,6 @@ These options work with plain text searches. Use Polars regex patterns in expres
408
409
  **Quick Tips:**
409
410
  - Search results highlight matching rows in **red**
410
411
  - Use expression for advanced selection (e.g., $attack > $defense)
411
- - Multiple searches **accumulate** - each new search adds to the selections or matches
412
412
  - Type-aware matching automatically converts values. Resort to string comparison if conversion fails
413
413
  - Use `u` to undo any search or filter
414
414
 
@@ -587,7 +587,7 @@ This is useful for:
587
587
  - Quick statistical summaries without external tools
588
588
  - Comparing statistics across columns
589
589
 
590
- ### 11. Data Editing
590
+ ### 11. Editing
591
591
 
592
592
  **Edit Cell** (`e` or **Double-click**):
593
593
  - Opens modal for editing current cell
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dataframe-textual"
7
- version = "2.7.0"
7
+ version = "2.9.0"
8
8
  description = "Interactive terminal viewer/editor for tabular data"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -161,10 +161,21 @@ class DataFrameTable(DataTable):
161
161
  - **,** - 🔢 Toggle thousand separator for numeric display
162
162
  - **K** - 🔄 Cycle cursor (cell → row → column → cell)
163
163
 
164
- ## ↕️ Sorting
165
- - **[** - 🔼 Sort column ascending
166
- - **]** - 🔽 Sort column descending
167
- - *(Multi-column sort supported)*
164
+ ## ✏️ Editing
165
+ - **Double-click** - ✍️ Edit cell or rename column header
166
+ - **e** - ✍️ Edit current cell
167
+ - **E** - 📊 Edit entire column with expression
168
+ - **a** - ➕ Add empty column after current
169
+ - **A** - ➕ Add column with name and optional expression
170
+ - **@** - 🔗 Add a new link column from template
171
+ - **x** - ❌ Delete current row
172
+ - **X** - ❌ Delete row and those below
173
+ - **Ctrl+X** - ❌ Delete row and those above
174
+ - **delete** - ❌ Clear current cell (set to NULL)
175
+ - **Shift+Delete** - ❌ Clear current column (set matching cells to NULL)
176
+ - **-** - ❌ Delete current column
177
+ - **d** - 📋 Duplicate current column
178
+ - **D** - 📋 Duplicate current row
168
179
 
169
180
  ## ✅ Row Selection
170
181
  - **\\\\** - ✅ Select rows with cell matches or those matching cursor value in current column
@@ -188,29 +199,15 @@ class DataFrameTable(DataTable):
188
199
  - *(Supports case-insensitive & whole-word matching)*
189
200
 
190
201
  ## 👁️ View & Filter
191
- - **"** - 📍 Filter selected rows (removes others)
192
- - **v** - 👁️ View selected rows (hides others)
193
- - **V** - 🔧 View selected rows matching expression (hides others)
194
-
195
- ## 🔍 SQL Interface
196
- - **l** - 💬 Open simple SQL interface (select columns & where clause)
197
- - **L** - 🔎 Open advanced SQL interface (full SQL queries)
202
+ - **"** - 📍 Filter selected rows (others removed)
203
+ - **.** - 👁️ View rows with non-null values in current column (others hidden)
204
+ - **v** - 👁️ View selected rows (others hidden)
205
+ - **V** - 🔧 View selected rows matching expression (others hidden)
198
206
 
199
- ## ✏️ Editing
200
- - **Double-click** - ✍️ Edit cell or rename column header
201
- - **e** - ✍️ Edit current cell
202
- - **E** - 📊 Edit entire column with expression
203
- - **a** - ➕ Add empty column after current
204
- - **A** - ➕ Add column with name and optional expression
205
- - **@** - 🔗 Add a new link column from template
206
- - **x** - ❌ Delete current row
207
- - **X** - ❌ Delete row and those below
208
- - **Ctrl+X** - ❌ Delete row and those above
209
- - **delete** - ❌ Clear current cell (set to NULL)
210
- - **Shift+Delete** - ❌ Clear current column (set matching cells to NULL)
211
- - **-** - ❌ Delete current column
212
- - **d** - 📋 Duplicate current column
213
- - **D** - 📋 Duplicate current row
207
+ ## ↕️ Sorting
208
+ - **[** - 🔼 Sort column ascending
209
+ - **]** - 🔽 Sort column descending
210
+ - *(Multi-column sort supported)*
214
211
 
215
212
  ## 🎯 Reorder
216
213
  - **Shift+↑↓** - ⬆️⬇️ Move row up/down
@@ -226,6 +223,10 @@ class DataFrameTable(DataTable):
226
223
  - **c** - 📋 Copy cell to clipboard
227
224
  - **Ctrl+c** - 📊 Copy column to clipboard
228
225
  - **Ctrl+r** - 📝 Copy row to clipboard (tab-separated)
226
+
227
+ ## 🔍 SQL Interface
228
+ - **l** - 💬 Open simple SQL interface (select columns & where clause)
229
+ - **L** - 🔎 Open advanced SQL interface (full SQL queries)
229
230
  """).strip()
230
231
 
231
232
  # fmt: off
@@ -264,6 +265,7 @@ class DataFrameTable(DataTable):
264
265
  ("left_square_bracket", "sort_ascending", "Sort ascending"), # `[`
265
266
  ("right_square_bracket", "sort_descending", "Sort descending"), # `]`
266
267
  # View & Filter
268
+ ("full_stop", "view_rows_non_null", "View rows with non-null values in current column"),
267
269
  ("v", "view_rows", "View selected rows"),
268
270
  ("V", "view_rows_expr", "View selected rows matching expression"),
269
271
  ("quotation_mark", "filter_rows", "Filter selected rows"), # `"`
@@ -761,6 +763,10 @@ class DataFrameTable(DataTable):
761
763
  """Show metadata for the current column."""
762
764
  self.do_metadata_column()
763
765
 
766
+ def action_view_rows_non_null(self) -> None:
767
+ """View rows with non-null values in the current column."""
768
+ self.do_view_rows_non_null()
769
+
764
770
  def action_view_rows(self) -> None:
765
771
  """View rows by current cell value."""
766
772
  self.do_view_rows()
@@ -2876,7 +2882,7 @@ class DataFrameTable(DataTable):
2876
2882
  expr = validate_expr(term, self.df.columns, cidx)
2877
2883
  except Exception as e:
2878
2884
  self.notify(
2879
- f"Error validating expression [$error]{term}[/]", title="Search", severity="error", timeout=10
2885
+ f"Error validating expression [$error]{term}[/]", title="Select Row", severity="error", timeout=10
2880
2886
  )
2881
2887
  self.log(f"Error validating expression `{term}`: {str(e)}")
2882
2888
  return
@@ -2902,7 +2908,7 @@ class DataFrameTable(DataTable):
2902
2908
  expr = pl.col(col_name).cast(pl.String).str.contains(term)
2903
2909
  self.notify(
2904
2910
  f"Error converting [$error]{term}[/] to [$accent]{dtype}[/]. Cast to string.",
2905
- title="Search",
2911
+ title="Select Row",
2906
2912
  severity="warning",
2907
2913
  )
2908
2914
 
@@ -2914,7 +2920,7 @@ class DataFrameTable(DataTable):
2914
2920
  ok_rids = set(lf.filter(expr).collect()[RID])
2915
2921
  except Exception as e:
2916
2922
  self.notify(
2917
- f"Error applying search filter `[$error]{term}[/]`", title="Search", severity="error", timeout=10
2923
+ f"Error applying search filter `[$error]{term}[/]`", title="Select Row", severity="error", timeout=10
2918
2924
  )
2919
2925
  self.log(f"Error applying search filter `{term}`: {str(e)}")
2920
2926
  return
@@ -2923,7 +2929,7 @@ class DataFrameTable(DataTable):
2923
2929
  if match_count == 0:
2924
2930
  self.notify(
2925
2931
  f"No matches found for `[$warning]{term}[/]`. Try [$accent](?i)abc[/] for case-insensitive search.",
2926
- title="Search",
2932
+ title="Select Row",
2927
2933
  severity="warning",
2928
2934
  )
2929
2935
  return
@@ -2933,8 +2939,8 @@ class DataFrameTable(DataTable):
2933
2939
  # Add to history
2934
2940
  self.add_history(message)
2935
2941
 
2936
- # Update selected rows to include new selections
2937
- self.selected_rows.update(ok_rids)
2942
+ # Update selected rows
2943
+ self.selected_rows = ok_rids
2938
2944
 
2939
2945
  # Show notification immediately, then start highlighting
2940
2946
  self.notify(message, title="Select Row")
@@ -3042,7 +3048,7 @@ class DataFrameTable(DataTable):
3042
3048
  else:
3043
3049
  columns_to_search = list(enumerate(self.df.columns))
3044
3050
 
3045
- # Search each column consistently
3051
+ # Handle each column consistently
3046
3052
  for col_idx, col_name in columns_to_search:
3047
3053
  # Build expression based on term type
3048
3054
  if term == NULL:
@@ -3102,8 +3108,9 @@ class DataFrameTable(DataTable):
3102
3108
  cidx = self.cursor_col_idx if scope == "column" else None
3103
3109
 
3104
3110
  # Push the search modal screen
3111
+
3105
3112
  self.app.push_screen(
3106
- SearchScreen("Find", term, self.df, cidx),
3113
+ SearchScreen("Find" if scope == "column" else "Global Find", term, self.df, cidx),
3107
3114
  callback=self.find if scope == "column" else self.find_global,
3108
3115
  )
3109
3116
 
@@ -3133,10 +3140,9 @@ class DataFrameTable(DataTable):
3133
3140
  # Add to history
3134
3141
  self.add_history(f"Found `[$success]{term}[/]` in column [$accent]{col_name}[/]")
3135
3142
 
3136
- # Add to matches and count total
3143
+ # Update matches and count total
3137
3144
  match_count = sum(len(cols) for cols in matches.values())
3138
- for rid, cols in matches.items():
3139
- self.matches[rid].update(cols)
3145
+ self.matches = matches
3140
3146
 
3141
3147
  self.notify(f"Found [$success]{match_count}[/] matches for `[$accent]{term}[/]`", title="Find")
3142
3148
 
@@ -3167,10 +3173,9 @@ class DataFrameTable(DataTable):
3167
3173
  # Add to history
3168
3174
  self.add_history(f"Found `[$success]{term}[/]` across all columns")
3169
3175
 
3170
- # Add to matches and count total
3176
+ # Update matches and count total
3171
3177
  match_count = sum(len(cols) for cols in matches.values())
3172
- for rid, cols in matches.items():
3173
- self.matches[rid].update(cols)
3178
+ self.matches = matches
3174
3179
 
3175
3180
  self.notify(
3176
3181
  f"Found [$success]{match_count}[/] matches for `[$accent]{term}[/]` across all columns",
@@ -3278,7 +3283,7 @@ class DataFrameTable(DataTable):
3278
3283
  """Open replace screen for current column."""
3279
3284
  # Push the replace modal screen
3280
3285
  self.app.push_screen(
3281
- FindReplaceScreen(self, title="Find and Replace in Current Column"),
3286
+ FindReplaceScreen(self, title="Find and Replace"),
3282
3287
  callback=self.replace,
3283
3288
  )
3284
3289
 
@@ -3603,6 +3608,15 @@ class DataFrameTable(DataTable):
3603
3608
  self.show_next_replace_confirmation()
3604
3609
 
3605
3610
  # View & Filter
3611
+ def do_view_rows_non_null(self) -> None:
3612
+ """View non-null rows based on the cursor column."""
3613
+ cidx = self.cursor_col_idx
3614
+ col_name = self.cursor_col_name
3615
+
3616
+ term = pl.col(col_name).is_not_null()
3617
+
3618
+ self.view_rows((term, cidx, False, True))
3619
+
3606
3620
  def do_view_rows(self) -> None:
3607
3621
  """View rows.
3608
3622
 
@@ -3712,7 +3726,7 @@ class DataFrameTable(DataTable):
3712
3726
  return
3713
3727
 
3714
3728
  # Add to history
3715
- self.add_history(f"Filtered by expression [$success]{expr_str}[/]")
3729
+ self.add_history(f"Viewed rows by expression [$success]{expr_str}[/]")
3716
3730
 
3717
3731
  ok_rids = set(df_filtered[RID])
3718
3732
 
@@ -3734,7 +3748,7 @@ class DataFrameTable(DataTable):
3734
3748
  # Recreate table for display
3735
3749
  self.setup_table()
3736
3750
 
3737
- self.notify(f"Filtered to [$success]{matched_count}[/] matching row(s)", title="View Rows")
3751
+ self.notify(f"Showing [$success]{matched_count}[/] matching row(s)", title="View Rows")
3738
3752
 
3739
3753
  def do_filter_rows(self) -> None:
3740
3754
  """Filter rows.
@@ -449,13 +449,7 @@ class SearchScreen(YesNoScreen):
449
449
  self.cidx = cidx
450
450
 
451
451
  EXPR = f"ABC, (?i)abc, ^abc$, {NULL}, $_ > 50, $1 < $HP, $_.str.contains('sub')"
452
-
453
- if "Search" in title:
454
- col_name = df.columns[cidx]
455
- col_dtype = df.dtypes[cidx]
456
- label = f"{title} in [$success]{col_name}[/] ([$warning]{col_dtype}[/]) with value or Polars expression, e.g., {EXPR}"
457
- else:
458
- label = f"{title} by value or Polars expression, e.g., {EXPR}"
452
+ label = f"By value or Polars expression, e.g., {EXPR}"
459
453
 
460
454
  super().__init__(
461
455
  title=title,
@@ -171,7 +171,7 @@ wheels = [
171
171
 
172
172
  [[package]]
173
173
  name = "dataframe-textual"
174
- version = "2.7.0"
174
+ version = "2.9.0"
175
175
  source = { editable = "." }
176
176
  dependencies = [
177
177
  { name = "polars" },