dataframe-textual 2.5.0__tar.gz → 2.6.2__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.
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/PKG-INFO +3 -2
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/README.md +2 -1
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/pyproject.toml +1 -1
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/data_frame_table.py +104 -60
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/uv.lock +1 -1
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/.gitignore +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/1811.csv.gz +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/LICENSE +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/large_malformed.tsv.gz +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/main.py +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/__init__.py +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/__main__.py +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/common.py +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/data_frame_help_panel.py +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/data_frame_viewer.py +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/sql_screen.py +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/table_screen.py +0 -0
- {dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/yes_no_screen.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataframe-textual
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.2
|
|
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
|
|
@@ -314,7 +314,8 @@ zcat compressed_data.csv.gz | dv -f csv
|
|
|
314
314
|
| Key | Action |
|
|
315
315
|
|-----|--------|
|
|
316
316
|
| `Double-click` | Edit cell or rename column header |
|
|
317
|
-
| `
|
|
317
|
+
| `Delete` | Clear current cell (set to NULL) |
|
|
318
|
+
| `Shift+Delete` | Clear current column (set matching cells to NULL) |
|
|
318
319
|
| `e` | Edit current cell (respects data type) |
|
|
319
320
|
| `E` | Edit entire column with value/expression |
|
|
320
321
|
| `a` | Add empty column after current |
|
|
@@ -275,7 +275,8 @@ zcat compressed_data.csv.gz | dv -f csv
|
|
|
275
275
|
| Key | Action |
|
|
276
276
|
|-----|--------|
|
|
277
277
|
| `Double-click` | Edit cell or rename column header |
|
|
278
|
-
| `
|
|
278
|
+
| `Delete` | Clear current cell (set to NULL) |
|
|
279
|
+
| `Shift+Delete` | Clear current column (set matching cells to NULL) |
|
|
279
280
|
| `e` | Edit current cell (respects data type) |
|
|
280
281
|
| `E` | Edit entire column with value/expression |
|
|
281
282
|
| `a` | Add empty column after current |
|
{dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/data_frame_table.py
RENAMED
|
@@ -207,6 +207,7 @@ class DataFrameTable(DataTable):
|
|
|
207
207
|
- **X** - ❌ Delete row and those below
|
|
208
208
|
- **Ctrl+X** - ❌ Delete row and those above
|
|
209
209
|
- **delete** - ❌ Clear current cell (set to NULL)
|
|
210
|
+
- **Shift+Delete** - ❌ Clear current column (set matching cells to NULL)
|
|
210
211
|
- **-** - ❌ Delete current column
|
|
211
212
|
- **d** - 📋 Duplicate current column
|
|
212
213
|
- **D** - 📋 Duplicate current row
|
|
@@ -285,6 +286,7 @@ class DataFrameTable(DataTable):
|
|
|
285
286
|
("R", "replace_global", "Replace global"), # `Shift+R`
|
|
286
287
|
# Delete
|
|
287
288
|
("delete", "clear_cell", "Clear cell"),
|
|
289
|
+
("shift+delete", "clear_column", "Clear cells in current column that match cursor value"), # `Shift+Delete`
|
|
288
290
|
("minus", "delete_column", "Delete column"), # `-`
|
|
289
291
|
("x", "delete_row", "Delete row"),
|
|
290
292
|
("X", "delete_row_and_below", "Delete row and those below"),
|
|
@@ -795,6 +797,10 @@ class DataFrameTable(DataTable):
|
|
|
795
797
|
"""Clear the current cell (set to None)."""
|
|
796
798
|
self.do_clear_cell()
|
|
797
799
|
|
|
800
|
+
def action_clear_column(self) -> None:
|
|
801
|
+
"""Clear cells in the current column that match the cursor value."""
|
|
802
|
+
self.do_clear_column()
|
|
803
|
+
|
|
798
804
|
def action_select_row(self) -> None:
|
|
799
805
|
"""Select rows with cursor value in the current column."""
|
|
800
806
|
self.do_select_row()
|
|
@@ -1749,13 +1755,14 @@ class DataFrameTable(DataTable):
|
|
|
1749
1755
|
max_width = label_width
|
|
1750
1756
|
|
|
1751
1757
|
# Scan through all loaded rows that are visible to find max width
|
|
1752
|
-
for
|
|
1753
|
-
|
|
1754
|
-
|
|
1758
|
+
for row_start, row_end in self.loaded_ranges:
|
|
1759
|
+
for row_idx in range(row_start, row_end):
|
|
1760
|
+
cell_value = str(self.df.item(row_idx, col_idx))
|
|
1761
|
+
cell_width = measure(self.app.console, cell_value, 1)
|
|
1755
1762
|
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1763
|
+
if cell_width > max_width:
|
|
1764
|
+
need_expand = True
|
|
1765
|
+
max_width = cell_width
|
|
1759
1766
|
|
|
1760
1767
|
if not need_expand:
|
|
1761
1768
|
return
|
|
@@ -2043,25 +2050,10 @@ class DataFrameTable(DataTable):
|
|
|
2043
2050
|
# Also update the view if applicable
|
|
2044
2051
|
# Update the value of col_name in df_view using the value of col_name from df based on RID mapping between them
|
|
2045
2052
|
if self.df_view is not None:
|
|
2046
|
-
# Get updated column from df
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
RID, pl.col(col_name).alias(col_updated), pl.lit(True).alias(col_exists)
|
|
2051
|
-
)
|
|
2052
|
-
# Join and use when/then/otherwise to handle all updates including NULLs
|
|
2053
|
-
self.df_view = (
|
|
2054
|
-
self.df_view.lazy()
|
|
2055
|
-
.join(lf_updated, on=RID, how="left")
|
|
2056
|
-
.with_columns(
|
|
2057
|
-
pl.when(pl.col(col_exists))
|
|
2058
|
-
.then(pl.col(col_updated))
|
|
2059
|
-
.otherwise(pl.col(col_name))
|
|
2060
|
-
.alias(col_name)
|
|
2061
|
-
)
|
|
2062
|
-
.drop(col_updated, col_exists)
|
|
2063
|
-
.collect()
|
|
2064
|
-
)
|
|
2053
|
+
# Get updated column from df
|
|
2054
|
+
lf_updated = self.df.lazy().select(RID, pl.col(col_name))
|
|
2055
|
+
# Update df_view by joining on RID
|
|
2056
|
+
self.df_view = self.df_view.lazy().update(lf_updated, on=RID, include_nulls=True).collect()
|
|
2065
2057
|
except Exception as e:
|
|
2066
2058
|
self.notify(
|
|
2067
2059
|
f"Error applying expression: [$error]{term}[/] to column [$accent]{col_name}[/]",
|
|
@@ -2147,18 +2139,26 @@ class DataFrameTable(DataTable):
|
|
|
2147
2139
|
|
|
2148
2140
|
# Update the cell to None in the dataframe
|
|
2149
2141
|
try:
|
|
2150
|
-
self.df =
|
|
2151
|
-
|
|
2152
|
-
.
|
|
2153
|
-
|
|
2154
|
-
|
|
2142
|
+
self.df = (
|
|
2143
|
+
self.df.lazy()
|
|
2144
|
+
.with_columns(
|
|
2145
|
+
pl.when(pl.arange(0, len(self.df)) == ridx)
|
|
2146
|
+
.then(pl.lit(None))
|
|
2147
|
+
.otherwise(pl.col(col_name))
|
|
2148
|
+
.alias(col_name)
|
|
2149
|
+
)
|
|
2150
|
+
.collect()
|
|
2155
2151
|
)
|
|
2156
2152
|
|
|
2157
2153
|
# Also update the view if applicable
|
|
2158
2154
|
if self.df_view is not None:
|
|
2159
2155
|
ridx_view = self.df.item(ridx, self.df.columns.index(RID))
|
|
2160
|
-
self.df_view =
|
|
2161
|
-
|
|
2156
|
+
self.df_view = (
|
|
2157
|
+
self.df_view.lazy()
|
|
2158
|
+
.with_columns(
|
|
2159
|
+
pl.when(pl.col(RID) == ridx_view).then(pl.lit(None)).otherwise(pl.col(col_name)).alias(col_name)
|
|
2160
|
+
)
|
|
2161
|
+
.collect()
|
|
2162
2162
|
)
|
|
2163
2163
|
|
|
2164
2164
|
# Update the display
|
|
@@ -2177,7 +2177,43 @@ class DataFrameTable(DataTable):
|
|
|
2177
2177
|
timeout=10,
|
|
2178
2178
|
)
|
|
2179
2179
|
self.log(f"Error clearing cell ({ridx}, {col_name}): {str(e)}")
|
|
2180
|
-
|
|
2180
|
+
|
|
2181
|
+
def do_clear_column(self) -> None:
|
|
2182
|
+
"""Clear the current column by setting all its values to None."""
|
|
2183
|
+
col_idx = self.cursor_column
|
|
2184
|
+
col_name = self.cursor_col_name
|
|
2185
|
+
value = self.cursor_value
|
|
2186
|
+
|
|
2187
|
+
# Add to history
|
|
2188
|
+
self.add_history(f"Cleared column [$success]{col_name}[/]", dirty=True)
|
|
2189
|
+
|
|
2190
|
+
try:
|
|
2191
|
+
# Update the entire column to None in the dataframe
|
|
2192
|
+
self.df = (
|
|
2193
|
+
self.df.lazy()
|
|
2194
|
+
.with_columns(
|
|
2195
|
+
pl.when(pl.col(col_name) == value).then(pl.lit(None)).otherwise(pl.col(col_name)).alias(col_name)
|
|
2196
|
+
)
|
|
2197
|
+
.collect()
|
|
2198
|
+
)
|
|
2199
|
+
|
|
2200
|
+
# Also update the view if applicable
|
|
2201
|
+
if self.df_view is not None:
|
|
2202
|
+
lf_updated = self.df.lazy().select(RID, pl.col(col_name))
|
|
2203
|
+
self.df_view = self.df_view.lazy().update(lf_updated, on=RID, include_nulls=True).collect()
|
|
2204
|
+
|
|
2205
|
+
# Recreate table for display
|
|
2206
|
+
self.setup_table()
|
|
2207
|
+
|
|
2208
|
+
# Move cursor to the cleared column
|
|
2209
|
+
self.move_cursor(column=col_idx)
|
|
2210
|
+
|
|
2211
|
+
# self.notify(f"Cleared column [$success]{col_name}[/]", title="Clear Column")
|
|
2212
|
+
except Exception as e:
|
|
2213
|
+
self.notify(
|
|
2214
|
+
f"Error clearing column [$error]{col_name}[/]", title="Clear Column", severity="error", timeout=10
|
|
2215
|
+
)
|
|
2216
|
+
self.log(f"Error clearing column `{col_name}`: {str(e)}")
|
|
2181
2217
|
|
|
2182
2218
|
def do_add_column(self, col_name: str = None) -> None:
|
|
2183
2219
|
"""Add acolumn after the current column."""
|
|
@@ -2961,10 +2997,10 @@ class DataFrameTable(DataTable):
|
|
|
2961
2997
|
# self.notify("No selections to clear", title="Clear Selections and Matches", severity="warning")
|
|
2962
2998
|
return
|
|
2963
2999
|
|
|
2964
|
-
row_count = len(self.selected_rows | set(self.matches.keys()))
|
|
3000
|
+
# row_count = len(self.selected_rows | set(self.matches.keys()))
|
|
2965
3001
|
|
|
2966
3002
|
# Add to history
|
|
2967
|
-
self.add_history("Cleared all
|
|
3003
|
+
self.add_history("Cleared all selections and matches")
|
|
2968
3004
|
|
|
2969
3005
|
# Clear all selections
|
|
2970
3006
|
self.selected_rows = set()
|
|
@@ -2973,7 +3009,7 @@ class DataFrameTable(DataTable):
|
|
|
2973
3009
|
# Recreate table for display
|
|
2974
3010
|
self.setup_table()
|
|
2975
3011
|
|
|
2976
|
-
self.notify(f"Cleared selections for [$success]{row_count}[/] rows", title="Clear Selections and Matches")
|
|
3012
|
+
# self.notify(f"Cleared selections for [$success]{row_count}[/] rows", title="Clear Selections and Matches")
|
|
2977
3013
|
|
|
2978
3014
|
# Find & Replace
|
|
2979
3015
|
def find_matches(
|
|
@@ -3385,18 +3421,23 @@ class DataFrameTable(DataTable):
|
|
|
3385
3421
|
# Only applicable to string columns for substring matches
|
|
3386
3422
|
if dtype == pl.String and not state.match_whole:
|
|
3387
3423
|
term_find = f"(?i){state.term_find}" if state.match_nocase else state.term_find
|
|
3424
|
+
new_value = (
|
|
3425
|
+
pl.lit(None)
|
|
3426
|
+
if state.term_replace == NULL
|
|
3427
|
+
else pl.col(col_name).str.replace_all(term_find, state.term_replace)
|
|
3428
|
+
)
|
|
3388
3429
|
self.df = self.df.with_columns(
|
|
3389
|
-
pl.when(mask)
|
|
3390
|
-
.then(pl.col(col_name).str.replace_all(term_find, state.term_replace))
|
|
3391
|
-
.otherwise(pl.col(col_name))
|
|
3392
|
-
.alias(col_name)
|
|
3430
|
+
pl.when(mask).then(new_value).otherwise(pl.col(col_name)).alias(col_name)
|
|
3393
3431
|
)
|
|
3394
3432
|
else:
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3433
|
+
if state.term_replace == NULL:
|
|
3434
|
+
value = None
|
|
3435
|
+
else:
|
|
3436
|
+
# Try to convert replacement value to column dtype
|
|
3437
|
+
try:
|
|
3438
|
+
value = DtypeConfig(dtype).convert(state.term_replace)
|
|
3439
|
+
except Exception:
|
|
3440
|
+
value = state.term_replace
|
|
3400
3441
|
|
|
3401
3442
|
self.df = self.df.with_columns(
|
|
3402
3443
|
pl.when(mask).then(pl.lit(value)).otherwise(pl.col(col_name)).alias(col_name)
|
|
@@ -3404,15 +3445,8 @@ class DataFrameTable(DataTable):
|
|
|
3404
3445
|
|
|
3405
3446
|
# Also update the view if applicable
|
|
3406
3447
|
if self.df_view is not None:
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
self.df_view = (
|
|
3410
|
-
self.df_view.lazy()
|
|
3411
|
-
.join(lf_updated, on=RID, how="left")
|
|
3412
|
-
.with_columns(pl.coalesce(pl.col(col_updated), pl.col(col_name)).alias(col_name))
|
|
3413
|
-
.drop(col_updated)
|
|
3414
|
-
.collect()
|
|
3415
|
-
)
|
|
3448
|
+
lf_updated = self.df.lazy().filter(mask).select(pl.col(RID), pl.col(col_name))
|
|
3449
|
+
self.df_view = self.df_view.lazy().update(lf_updated, on=RID, include_nulls=True).collect()
|
|
3416
3450
|
|
|
3417
3451
|
state.replaced_occurrence += len(ridxs)
|
|
3418
3452
|
|
|
@@ -3491,9 +3525,14 @@ class DataFrameTable(DataTable):
|
|
|
3491
3525
|
# Only applicable to string columns for substring matches
|
|
3492
3526
|
if dtype == pl.String and not state.match_whole:
|
|
3493
3527
|
term_find = f"(?i){state.term_find}" if state.match_nocase else state.term_find
|
|
3528
|
+
new_value = (
|
|
3529
|
+
pl.lit(None)
|
|
3530
|
+
if state.term_replace == NULL
|
|
3531
|
+
else pl.col(col_name).str.replace_all(term_find, state.term_replace)
|
|
3532
|
+
)
|
|
3494
3533
|
self.df = self.df.with_columns(
|
|
3495
3534
|
pl.when(pl.arange(0, len(self.df)) == ridx)
|
|
3496
|
-
.then(
|
|
3535
|
+
.then(new_value)
|
|
3497
3536
|
.otherwise(pl.col(col_name))
|
|
3498
3537
|
.alias(col_name)
|
|
3499
3538
|
)
|
|
@@ -3507,11 +3546,14 @@ class DataFrameTable(DataTable):
|
|
|
3507
3546
|
.alias(col_name)
|
|
3508
3547
|
)
|
|
3509
3548
|
else:
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3549
|
+
if state.term_replace == NULL:
|
|
3550
|
+
value = None
|
|
3551
|
+
else:
|
|
3552
|
+
# try to convert replacement value to column dtype
|
|
3553
|
+
try:
|
|
3554
|
+
value = DtypeConfig(dtype).convert(state.term_replace)
|
|
3555
|
+
except Exception:
|
|
3556
|
+
value = state.term_replace
|
|
3515
3557
|
|
|
3516
3558
|
self.df = self.df.with_columns(
|
|
3517
3559
|
pl.when(pl.arange(0, len(self.df)) == ridx)
|
|
@@ -3539,6 +3581,8 @@ class DataFrameTable(DataTable):
|
|
|
3539
3581
|
if not state.done:
|
|
3540
3582
|
# Get the new value of the current cell after replacement
|
|
3541
3583
|
new_cell_value = self.df.item(ridx, cidx)
|
|
3584
|
+
if new_cell_value is None:
|
|
3585
|
+
new_cell_value = NULL_DISPLAY
|
|
3542
3586
|
row_key = str(ridx)
|
|
3543
3587
|
col_key = col_name
|
|
3544
3588
|
self.update_cell(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/data_frame_help_panel.py
RENAMED
|
File without changes
|
{dataframe_textual-2.5.0 → dataframe_textual-2.6.2}/src/dataframe_textual/data_frame_viewer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|