Skip to content

Commit 65e9953

Browse files
Claude Codeclaude
andcommitted
Fix all mypy type errors across the codebase
- Remove unused abstract methods (save_state, load_state) from HasState - Replace Style | None with Style.null() defaults to eliminate union-attr errors - Add _color_name() helper for safe Color | None access - Fix type annotations: struct dict params, table_name Path | str, epoch Literal - Add early return guard in action_iter_search for None-safe queue/idx access - Use cast() for polars .max() return values in _measure() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5f28b2d commit 65e9953

5 files changed

Lines changed: 42 additions & 63 deletions

File tree

pyproject.toml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,6 @@ asyncio_mode = "auto"
4444

4545
[tool.mypy]
4646
ignore_missing_imports = true
47-
disable_error_code = [
48-
"union-attr",
49-
"index",
50-
"assignment",
51-
"attr-defined",
52-
"arg-type",
53-
"operator",
54-
"return-value",
55-
"abstract",
56-
"type-abstract",
57-
]
5847

5948
[tool.ruff]
6049
line-length = 120

src/dt_browser/__init__.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,7 @@
3535

3636

3737
class HasState:
38-
@abstractmethod
39-
def save_state(self, existing: dict) -> dict:
40-
"""
41-
Generate any persistent data from this object
42-
43-
Args:
44-
existing: Any existing state for this object which should be merged with the current state.
45-
(e.g if there are multiple instances of the browser which should be merged into a single state object)
46-
"""
47-
48-
@abstractmethod
49-
def load_state(self, state: dict, table_name: str, df: pl.DataFrame):
50-
"""
51-
Apply the provided state to the current object
52-
53-
Args:
54-
state: the state
55-
table_name: the current table name being displayed
56-
df: The full dataframe being displayed
57-
"""
38+
pass
5839

5940

6041
class ReactiveLabel(Label):

src/dt_browser/browser.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import gc
33
import pathlib
44
import time
5-
from typing import ClassVar
5+
from typing import Any, ClassVar, Literal
66

77
import click
88
import polars as pl
@@ -29,7 +29,7 @@
2929
)
3030
from dt_browser.bookmarks import Bookmarks
3131
from dt_browser.column_selector import ColumnSelector
32-
from dt_browser.custom_table import CustomTable, polars_list_to_string
32+
from dt_browser.custom_table import CustomTable, _color_name, polars_list_to_string
3333
from dt_browser.filter_box import FilterBox
3434
from dt_browser.save_df_modal import SaveModal
3535
from dt_browser.suggestor import ColumnNameSuggestor
@@ -72,32 +72,32 @@ class TableWithBookmarks(CustomTable):
7272
def __init__(self, *args, bookmarks: Bookmarks, **kwargs):
7373
super().__init__(*args, **kwargs)
7474
self._bookmarks = bookmarks
75-
self._bookmark_highlight: Style | None = None
76-
self._search_highlight: Style | None = None
75+
self._bookmark_highlight: Style = Style.null()
76+
self._search_highlight: Style = Style.null()
7777

7878
def on_mount(self):
7979
self._bookmark_highlight = self.get_component_rich_style("datatable--row-bookmark")
8080
self._search_highlight = self.get_component_rich_style("datatable--row-search-result")
8181

82-
def _get_sel_col_bg_color(self, struct: pl.Struct):
82+
def _get_sel_col_bg_color(self, struct: dict[str, Any]) -> str:
8383
if self.active_search_queue and struct[INDEX_COL] in self.active_search_queue:
84-
return self._search_highlight.bgcolor.name
84+
return _color_name(self._search_highlight.bgcolor)
8585
if self._bookmarks.has_bookmarks and struct[INDEX_COL] in self._bookmarks.meta_dt[INDEX_COL]:
86-
return self._bookmark_highlight.bgcolor.name
86+
return _color_name(self._bookmark_highlight.bgcolor)
8787
return super()._get_sel_col_bg_color(struct)
8888

8989
def _get_row_bg_color_expr(self, cursor_row_idx: int) -> pl.Expr:
9090
tmp = super()._get_row_bg_color_expr(cursor_row_idx)
9191
if self.active_search_queue:
9292
tmp = (
9393
pl.when(pl.col(INDEX_COL).is_in(self.active_search_queue))
94-
.then(pl.lit(self._search_highlight.bgcolor.name))
94+
.then(pl.lit(_color_name(self._search_highlight.bgcolor)))
9595
.otherwise(tmp)
9696
)
9797
if self._bookmarks.has_bookmarks:
9898
tmp = (
9999
pl.when(pl.col(INDEX_COL).is_in(self._bookmarks.meta_dt[INDEX_COL]))
100-
.then(pl.lit(self._bookmark_highlight.bgcolor.name))
100+
.then(pl.lit(_color_name(self._bookmark_highlight.bgcolor)))
101101
.otherwise(tmp)
102102
)
103103
return tmp
@@ -108,7 +108,8 @@ def _get_row_bg_color_expr(self, cursor_row_idx: int) -> pl.Expr:
108108

109109
def _guess_timestamp_cols(df: pl.DataFrame):
110110
date_range = pl.Series(values=[datetime.date(2001, 1, 1), datetime.date(2042, 1, 1)])
111-
converts = [(x,) + tuple(date_range.dt.epoch(x)) for x in ("s", "ms", "us", "ns")]
111+
epoch_units: tuple[Literal["s", "ms", "us", "ns"], ...] = ("s", "ms", "us", "ns")
112+
converts = [(x,) + tuple(date_range.dt.epoch(x)) for x in epoch_units]
112113

113114
for col, dtype in df.schema.items():
114115
if dtype.is_integer():
@@ -342,7 +343,7 @@ class DtBrowser(Widget): # pylint: disable=too-many-public-methods,too-many-ins
342343
active_search: reactive[str | None] = reactive(None)
343344
# active_dt: reactive[pl.DataFrame] = reactive(pl.DataFrame(), init=False, always_update=True)
344345

345-
def __init__(self, table_name: str, source_file_or_table: pathlib.Path | pl.DataFrame):
346+
def __init__(self, table_name: str | pathlib.Path, source_file_or_table: pathlib.Path | pl.DataFrame):
346347
super().__init__()
347348
bt = (
348349
from_file_path(source_file_or_table)
@@ -463,7 +464,7 @@ async def watch_active_search(self, goto: bool = True):
463464
if goto:
464465
# Find the nearest index to the current cursor
465466
coord = self.query_one("#main_table", CustomTable).cursor_coordinate.row
466-
next_row = next((i for i, x in enumerate(self.active_search_queue) if x > coord), None)
467+
next_row = next((i for i, x in enumerate(self.active_search_queue) if x > coord), 0)
467468
self.active_search_idx = next_row - 1
468469
self.action_iter_search(True)
469470
except Exception as e:
@@ -472,12 +473,14 @@ async def watch_active_search(self, goto: bool = True):
472473
foot.search_pending = False
473474

474475
def action_iter_search(self, forward: bool):
476+
if self.active_search_queue is None or self.active_search_idx is None:
477+
return
475478
table = self.query_one("#main_table", CustomTable)
476479
coord = table.cursor_coordinate
477480
self.active_search_idx = min(
478481
max(self.active_search_idx + (1 if forward else -1), 0), len(self.active_search_queue) - 1
479482
)
480-
if self.active_search_idx >= 0 and self.active_search_idx < len(self.active_search_queue):
483+
if 0 <= self.active_search_idx < len(self.active_search_queue):
481484
next_idex = self.active_search_queue[self.active_search_idx]
482485
ys = next_idex
483486
xs = table.scroll_x
@@ -638,7 +641,7 @@ async def handle_cell_highlight(self, event: CustomTable.CellHighlighted):
638641
@on(CustomTable.CellSelected, selector="#main_table")
639642
def handle_cell_select(self, event: CustomTable.CellSelected):
640643
if self._select_interest:
641-
self.query_one(self._select_interest, ReceivesTableSelect).on_table_select(event.value)
644+
self.query_one(self._select_interest, ReceivesTableSelect).on_table_select(event.value) # type: ignore[type-abstract]
642645
self._select_interest = None
643646

644647
@on(Bookmarks.BookmarkSelected)
@@ -800,7 +803,7 @@ def compose(self) -> ComposeResult:
800803

801804

802805
class DtBrowserApp(App): # pylint: disable=too-many-public-methods,too-many-instance-attributes
803-
def __init__(self, table_name: str, source_file_or_table: pathlib.Path | pl.DataFrame):
806+
def __init__(self, table_name: str | pathlib.Path, source_file_or_table: pathlib.Path | pl.DataFrame):
804807
super().__init__()
805808
self._table_name = table_name
806809
self._source = source_file_or_table

src/dt_browser/custom_table.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import rich.repr
1111
from polars.interchange.protocol import Column
1212
from rich.align import Align
13+
from rich.color import Color
1314
from rich.console import Console, RenderableType
1415
from rich.errors import MarkupError
1516
from rich.markup import escape
@@ -28,6 +29,11 @@
2829
from dt_browser import COLOR_COL, COLORS, DISPLAY_IDX_COL, INDEX_COL
2930

3031

32+
def _color_name(color: Color | None) -> str:
33+
"""Extract the name from a Rich Color, defaulting to empty string if None."""
34+
return color.name if color is not None else ""
35+
36+
3137
def polars_list_to_string(column: pl.Expr):
3238
return pl.concat_str(pl.lit("["), column.cast(pl.List(pl.String)).list.join(", "), pl.lit("]")).alias(
3339
column.meta.output_name()
@@ -63,7 +69,7 @@ def cell_formatter(obj: object, null_rep: Text, col: Column | None = None) -> Re
6369
if isinstance(obj, (float, pl.Decimal)):
6470
return Align(f"{obj:n}", align="right")
6571
if isinstance(obj, int):
66-
if col is not None and col.is_id:
72+
if col is not None and getattr(col, "is_id", False):
6773
# no separators in ID fields
6874
return Align(str(obj), align="right")
6975
return Align(f"{obj:n}", align="right")
@@ -206,9 +212,9 @@ def __init__(
206212
self._cum_widths: dict[str, int] = {}
207213
self._formatters: dict[str, pl.Expr] = {}
208214

209-
self._cell_highlight: Style | None = None
210-
self._header_style: Style | None = None
211-
self._row_col_highlight: Style | None = None
215+
self._cell_highlight: Style = Style.null()
216+
self._header_style: Style = Style.null()
217+
self._row_col_highlight: Style = Style.null()
212218

213219
self._header: dict[str, Segment] = {}
214220
self._header_pad: list[Segment] = []
@@ -467,7 +473,7 @@ def on_key(self, event: events.Key) -> None:
467473
def _build_cast_expr(self, col: str, padding: int = 0):
468474
dtype = self._dt[col].dtype
469475
if dtype == pld.Categorical():
470-
dtype = pl.Utf8
476+
dtype = pl.String()
471477
as_str = pl.col(col).cast(pl.Utf8).fill_null("")
472478
if dtype.is_numeric() or dtype.is_temporal():
473479
return as_str.str.pad_start(padding)
@@ -605,17 +611,17 @@ def _get_row_bg_color_expr(self, cursor_row_idx: int) -> pl.Expr:
605611
pl.when(pl.col(DISPLAY_IDX_COL) == cursor_row_idx)
606612
.then(
607613
pl.lit(
608-
self._row_col_highlight.bgcolor.name
614+
_color_name(self._row_col_highlight.bgcolor)
609615
if self._cursor_type == CustomTable.CursorType.CELL
610-
else self._cell_highlight.bgcolor.name
616+
else _color_name(self._cell_highlight.bgcolor)
611617
)
612618
)
613619
.otherwise(pl.lit(None))
614620
)
615621

616-
def _get_sel_col_bg_color(self, struct: pl.Struct):
622+
def _get_sel_col_bg_color(self, struct: dict[str, Any]):
617623
return (
618-
self._row_col_highlight.bgcolor.name
624+
_color_name(self._row_col_highlight.bgcolor)
619625
if self._cursor_type == CustomTable.CursorType.CELL
620626
else struct["bgcolor"]
621627
)
@@ -644,7 +650,7 @@ def _gen_segments(self, lines: list[int] | None):
644650
PADDING_STR,
645651
style=Style(
646652
color=(
647-
self._cell_highlight.color.name
653+
_color_name(self._cell_highlight.color)
648654
if (
649655
self._cursor_type == CustomTable.CursorType.ROW
650656
and struct[DISPLAY_IDX_COL] == cursor_row_idx
@@ -658,7 +664,7 @@ def _gen_segments(self, lines: list[int] | None):
658664
struct["before_selected"],
659665
style=Style(
660666
color=(
661-
self._cell_highlight.color.name
667+
_color_name(self._cell_highlight.color)
662668
if (
663669
self._cursor_type == CustomTable.CursorType.ROW
664670
and struct[DISPLAY_IDX_COL] == cursor_row_idx
@@ -672,12 +678,12 @@ def _gen_segments(self, lines: list[int] | None):
672678
struct["selected"],
673679
style=Style(
674680
color=(
675-
self._cell_highlight.color.name
681+
_color_name(self._cell_highlight.color)
676682
if struct[DISPLAY_IDX_COL] == cursor_row_idx
677683
else struct[COLOR_COL]
678684
),
679685
bgcolor=(
680-
self._cell_highlight.bgcolor.name
686+
_color_name(self._cell_highlight.bgcolor)
681687
if struct[DISPLAY_IDX_COL] == cursor_row_idx
682688
else self._get_sel_col_bg_color(struct)
683689
),
@@ -687,7 +693,7 @@ def _gen_segments(self, lines: list[int] | None):
687693
struct["after_selected"],
688694
style=Style(
689695
color=(
690-
self._cell_highlight.color.name
696+
_color_name(self._cell_highlight.color)
691697
if (
692698
self._cursor_type == CustomTable.CursorType.ROW
693699
and struct[DISPLAY_IDX_COL] == cursor_row_idx
@@ -758,11 +764,11 @@ def _measure(self, arr: pl.Series) -> int:
758764

759765
if isinstance(dtype, pl.Array):
760766
base = arr.arr.eval(pl.element().cast(pl.String).str.len_chars())
761-
return (base.arr.sum() + (base.arr.len() - 1) * len(", ") + 2).max()
767+
return cast(int, (base.arr.sum() + (base.arr.len() - 1) * len(", ") + 2).max())
762768

763769
if isinstance(dtype, pl.List):
764770
base = arr.list.eval(pl.element().cast(pl.String).str.len_chars())
765-
return (base.list.sum() + (base.list.len() - 1) * len(", ") + 2).max()
771+
return cast(int, (base.list.sum() + (base.list.len() - 1) * len(", ") + 2).max())
766772

767773
if dtype.is_integer():
768774
col_max = arr.max()

src/dt_browser/filter_box.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def get_value(self):
107107
@on(ListView.Selected)
108108
def input_historical(self, event: ListView.Selected):
109109
box = self.query_one(Input)
110-
box.value = event.item.name
110+
box.value = event.item.name or ""
111111
box.focus()
112112

113113
def key_down(self):

0 commit comments

Comments
 (0)