From d40c607672e9aaac7ec038d3ede810e2bc053c5b Mon Sep 17 00:00:00 2001 From: Ahzyuan Date: Mon, 23 Jun 2025 17:06:15 +0800 Subject: [PATCH 1/2] fix: prevent error in rendering columns with all null values --- torchmeter/display.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/torchmeter/display.py b/torchmeter/display.py index 060baa2..045900c 100644 --- a/torchmeter/display.py +++ b/torchmeter/display.py @@ -648,6 +648,8 @@ def col_args(self, custom_args: Dict[str, Any]) -> None: self.col_args.mark_change() def df2tb(self, df: DataFrame, show_raw: bool = False) -> Table: + from polars import Null as pl_null + # create rich table tb_fields = df.columns tb = apply_setting( @@ -670,8 +672,10 @@ def df2tb(self, df: DataFrame, show_raw: bool = False) -> Table: return tb # collect each column's none replacing string - col_none_str = {col_name: getattr(df[col_name].drop_nulls()[0], "none_str", "-") - for col_name in df.schema} # fmt: skip + col_none_str = { + col_name: getattr(df[col_name].drop_nulls()[0], "none_str", "-") if not col_type.is_(pl_null) else "-" + for col_name, col_type in df.schema.items() + } # fmt: skip # fill table for vals_dict in df.iter_rows(named=True): From 219f95d3b5a6f87eb3f5d9261f123e3de1772ef3 Mon Sep 17 00:00:00 2001 From: Ahzyuan Date: Mon, 23 Jun 2025 17:36:06 +0800 Subject: [PATCH 2/2] test: add test cases for null type col rendering --- tests/test_display.py | 64 +++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/tests/test_display.py b/tests/test_display.py index 1c34b3f..7f868e2 100644 --- a/tests/test_display.py +++ b/tests/test_display.py @@ -158,15 +158,15 @@ def forward(self, x): @pytest.fixture def example_df(): """ - ┌─────────┬──────┬───────────┬───────────┬─────────────┐ - │ numeric ┆ text ┆ list_col ┆ nomal_obj ┆ self_obj │ - │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ - │ i64 ┆ str ┆ list[i64] ┆ object ┆ object │ - ╞═════════╪══════╪═══════════╪═══════════╪═════════════╡ - │ 1 ┆ a ┆ [1, 2] ┆ example ┆ 100 K │ - │ 2 ┆ null ┆ [3] ┆ dataframe ┆ null │ - │ null ┆ c ┆ null ┆ null ┆ 0.00 ± 0.00 │ - └─────────┴──────┴───────────┴───────────┴─────────────┘ + ┌─────────┬──────┬───────────┬──────────┬───────────┐ + │ numeric ┆ text ┆ list_col ┆ null_col ┆ nomal_obj │ + │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ + │ i64 ┆ str ┆ list[i64] ┆ null ┆ object │ + ╞═════════╪══════╪═══════════╪══════════╪═══════════╡ + │ 1 ┆ a ┆ [1, 2] ┆ null ┆ example │ + │ 2 ┆ null ┆ [3] ┆ null ┆ dataframe │ + │ null ┆ c ┆ null ┆ null ┆ null │ + └─────────┴──────┴───────────┴──────────┴───────────┘ """ from polars import Object as pl_obj @@ -175,6 +175,7 @@ def example_df(): "numeric": [1, 2, None], "text": ["a", None, "c"], "list_col": [[1, 2], [3], None], + "null_col": [None, None, None], "nomal_obj": [ Text("example"), Text("dataframe"), @@ -1339,16 +1340,16 @@ def test_df2tb_structure(self, simple_tabular_renderer, example_df) -> None: res = simple_tabular_renderer.df2tb(example_df, show_raw=False) assert isinstance(res, Table) - assert len(res.columns) == 5 + assert len(res.columns) == 6 assert res.row_count == 3 tb_headers = [col_obj.header for col_obj in res.columns] - assert tb_headers == ["numeric", "text", "list_col", "nomal_obj", "self_obj"] + assert tb_headers == ["numeric", "text", "list_col", "null_col", "nomal_obj", "self_obj"] assert str(example_df[0, 0]) == self.tbval_getter(0, 0, res) assert str(example_df[2, 1]) == self.tbval_getter(2, 1, res) assert str(example_df[1, 2].to_list()) == self.tbval_getter(1, 2, res) - assert str(example_df[0, 3]) == self.tbval_getter(0, 3, res) + assert str(example_df[0, 4]) == self.tbval_getter(0, 4, res) # 验证样式应用调用 mock_apply.assert_any_call( @@ -1378,11 +1379,16 @@ def test_df2tb_none_handling(self, simple_tabular_renderer, example_df) -> None: # list none assert self.tbval_getter(2, 2, res) == "-" - # normal object none + # null none + assert self.tbval_getter(0, 3, res) == "-" + assert self.tbval_getter(1, 3, res) == "-" assert self.tbval_getter(2, 3, res) == "-" + # normal object none + assert self.tbval_getter(2, 4, res) == "-" + # self object none - assert self.tbval_getter(1, 4, res) == "test none_str" + assert self.tbval_getter(1, 5, res) == "test none_str" def test_df2tb_show_raw(self, simple_tabular_renderer, example_df) -> None: """Test whether the show_raw argument works well""" @@ -1394,17 +1400,17 @@ def test_df2tb_show_raw(self, simple_tabular_renderer, example_df) -> None: assert self.tbval_getter(0, 0, noraml_res) == "1" assert self.tbval_getter(0, 1, noraml_res) == "a" assert self.tbval_getter(1, 2, noraml_res) == "[3]" - assert self.tbval_getter(1, 3, noraml_res) == "dataframe" - assert self.tbval_getter(0, 4, noraml_res) == "100 K" - assert self.tbval_getter(2, 4, noraml_res) == "0.00 ± 0.00" + assert self.tbval_getter(1, 4, noraml_res) == "dataframe" + assert self.tbval_getter(0, 5, noraml_res) == "100 K" + assert self.tbval_getter(2, 5, noraml_res) == "0.00 ± 0.00" # verify raw display assert self.tbval_getter(0, 0, raw_res) == "1" assert self.tbval_getter(0, 1, raw_res) == "a" assert self.tbval_getter(1, 2, raw_res) == "[3]" - assert self.tbval_getter(1, 3, raw_res) == "dataframe" - assert self.tbval_getter(0, 4, raw_res) == "100000.0" - assert self.tbval_getter(2, 4, raw_res) == "0.0" + assert self.tbval_getter(1, 4, raw_res) == "dataframe" + assert self.tbval_getter(0, 5, raw_res) == "100000.0" + assert self.tbval_getter(2, 5, raw_res) == "0.0" def test_clear(self, simple_tabular_renderer, example_df, monkeypatch) -> None: """Test the stat dataframe clearing logic""" @@ -1612,7 +1618,7 @@ def test_new_col(self, simple_tabular_renderer, example_df) -> None: col_func=lambda x: ["test"] * len(x), col_idx=0, ) - assert new_df.shape == (3, 6) + assert new_df.shape == (3, 7) assert new_df.columns[0] == "new_col" assert new_df["new_col"].to_list() == ["test"] * 3 @@ -1623,9 +1629,9 @@ def test_new_col(self, simple_tabular_renderer, example_df) -> None: col_func=lambda df: df.drop_in_place(name="numeric"), col_idx=0, ) - assert new_df.shape == (3, 6) - assert example_df.shape == (3, 5) - assert example_df.columns == ["numeric", "text", "list_col", "nomal_obj", "self_obj"] + assert new_df.shape == (3, 7) + assert example_df.shape == (3, 6) + assert example_df.columns == ["numeric", "text", "list_col", "null_col", "nomal_obj", "self_obj"] assert new_df["origin_numeric"].to_list() == example_df["numeric"].to_list() # verify col_idx @@ -1636,7 +1642,7 @@ def test_new_col(self, simple_tabular_renderer, example_df) -> None: col_func=lambda x: ["test"] * len(x), col_idx=1, ) - assert new_df.shape == (3, 6) + assert new_df.shape == (3, 7) assert new_df.columns[1] == "new_col" # non-negative and out of range (add last) @@ -1646,8 +1652,8 @@ def test_new_col(self, simple_tabular_renderer, example_df) -> None: col_func=lambda x: ["test"] * len(x), col_idx=8, ) - assert new_df.shape == (3, 6) - assert new_df.columns[5] == "new_col" + assert new_df.shape == (3, 7) + assert new_df.columns[6] == "new_col" # negative and in range new_df = new_col( @@ -1656,7 +1662,7 @@ def test_new_col(self, simple_tabular_renderer, example_df) -> None: col_func=lambda x: ["test"] * len(x), col_idx=-1, ) - assert new_df.shape == (3, 6) + assert new_df.shape == (3, 7) assert new_df.columns[-1] == "new_col" # negative and out of range (add first) @@ -1666,7 +1672,7 @@ def test_new_col(self, simple_tabular_renderer, example_df) -> None: col_func=lambda x: ["test"] * len(x), col_idx=-9, ) - assert new_df.shape == (3, 6) + assert new_df.shape == (3, 7) assert new_df.columns[0] == "new_col" # verify return_type is correctly applied