Skip to content

Commit 0edc3df

Browse files
committed
Handle empty dataframes without crashing
1 parent 3d1c322 commit 0edc3df

File tree

2 files changed

+35
-18
lines changed

2 files changed

+35
-18
lines changed

src/dt_browser/browser.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def __init__(self, *args, **kwargs):
245245
super().__init__(*args, **kwargs)
246246
self.border_title = "Row Detail"
247247
self._dt = CustomTable(
248-
pl.DataFrame(),
248+
pl.DataFrame({"Temp": []}),
249249
pl.DataFrame().with_row_index(name=INDEX_COL).select([INDEX_COL]),
250250
cursor_type=CustomTable.CursorType.NONE,
251251
)
@@ -461,7 +461,7 @@ async def watch_show_row_detail(self):
461461
if not self.show_row_detail:
462462
if existing := self.query(RowDetail):
463463
existing.remove()
464-
else:
464+
elif not self._display_dt.is_empty():
465465
await self.query_one("#main_hori", Horizontal).mount(self._row_detail)
466466

467467
async def action_show_bookmarks(self):
@@ -492,6 +492,8 @@ def _set_active_dt(self, active_dt: pl.DataFrame, new_row: int | None = None):
492492

493493
self._display_dt = active_dt
494494
self.cur_total_rows = len(self._display_dt)
495+
if self._display_dt.is_empty():
496+
self.show_row_detail = False
495497
self.watch_active_search(goto=False)
496498
(table := self.query_one("#main_table", CustomTable)).set_dt(self._display_dt, self._meta_dt)
497499
if new_row is not None:
@@ -529,9 +531,7 @@ async def watch_timestamp_columns(self):
529531
).alias(self._ts_col_names[x])
530532
for x in self.timestamp_columns
531533
]
532-
print(calc_expr)
533534
self._original_dt = self._original_dt.with_columns(calc_expr)
534-
print(self._original_dt.columns)
535535
except Exception as e:
536536
self.notify(f"Failed to compute timestamp columns: {e}", severity="warn", timeout=5)
537537
finally:
@@ -614,18 +614,20 @@ def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | No
614614
if not edtq.has_focus and action in (x.action if isinstance(x, Binding) else x[1] for x in DtBrowser.BINDINGS):
615615
return False
616616

617-
if action == "iter_search":
618-
if not self.active_search_queue:
619-
return False
620-
if bool(parameters[0]) and self.active_search_idx == len(self.active_search_queue) - 1:
621-
return False
622-
if not bool(parameters[0]) and self.active_search_idx == 0:
623-
return False
624-
if action == "show_bookmarks":
625-
return self._bookmarks.has_bookmarks
626-
627-
if action == "timestamp_selector":
628-
return len(self._ts_cols) > 0
617+
match action:
618+
case "iter_search":
619+
if not self.active_search_queue:
620+
return False
621+
if bool(parameters[0]) and self.active_search_idx == len(self.active_search_queue) - 1:
622+
return False
623+
if not bool(parameters[0]) and self.active_search_idx == 0:
624+
return False
625+
case "show_bookmarks":
626+
return self._bookmarks.has_bookmarks
627+
case "timestamp_selector":
628+
return len(self._ts_cols) > 0
629+
case "toggle_row_detail":
630+
return not self._display_dt.is_empty()
629631

630632
return True
631633

src/dt_browser/custom_table.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,17 @@ def _build_header_contents(self):
229229
)
230230

231231
_, header_width = self._build_base_header(self._dt.columns)
232-
self.virtual_size = Size(header_width, len(self._dt) + HEADER_HEIGHT)
232+
# Max to handle empty dataframe message
233+
self.virtual_size = Size(header_width, max(1, len(self._dt)) + HEADER_HEIGHT)
233234

234235
def set_metadata(self, metadata_dt: pl.DataFrame):
235236
self._metadata_dt = metadata_dt
236237
self._render_header_and_table = None
237238
self.refresh(repaint=True)
238239

239240
def set_dt(self, dt: pl.DataFrame, metadata_dt: pl.DataFrame):
241+
if not dt.columns:
242+
raise Exception("Cannot display a datatable with no columns")
240243
self._dt = dt
241244
self._metadata_dt = metadata_dt
242245
self._widths = {x: max(len(x), self._measure(self._dt[x])) for x in self._dt.columns}
@@ -577,7 +580,17 @@ def render_lines(self, crop: Region):
577580

578581
self._lines.clear()
579582
self._lines.append(cur_header)
580-
if not render_df.is_empty():
583+
if render_df.is_empty():
584+
msg = "< Empty Dataframe >"
585+
padding = int((self.scrollable_content_region.width - len(msg)) / 2)
586+
msg = f"{' '*padding}{msg}{' '*padding}"
587+
self._lines.append(
588+
Strip(
589+
[Segment(msg)],
590+
cell_length=self.scrollable_content_region.width,
591+
)
592+
)
593+
else:
581594
rend = render_df.lazy()
582595
if self._cursor_type == CustomTable.CursorType.NONE:
583596
rend = rend.select(
@@ -684,6 +697,8 @@ def _measure(self, arr: pl.Series) -> int:
684697
if dtype.is_(pld.Boolean()):
685698
return 7
686699

700+
if arr.is_empty():
701+
return 0
687702
# for everything else, we need to compute it
688703
width = arr.cast(pl.Utf8(), strict=False).fill_null("<null>").str.len_chars().max()
689704
assert isinstance(width, int)

0 commit comments

Comments
 (0)