From 2feaefecce143218741a050b08d7430dce441766 Mon Sep 17 00:00:00 2001 From: sakarpaisen Date: Sun, 11 Oct 2026 22:03:12 +0300 Subject: [PATCH] Report empty CSV column names `inspect_csv` now returns `empty_header_indices`: the zero-based positions of header cells that are empty or whitespace-only, so a blank column name is visible instead of only its effect on the row shape. The field is warning-only. `valid` stays true, the empty string stays in `columns`, and the count never moves. The empty-input branch adds an empty `empty_header_indices` list; a document that fails to parse keeps its current `{"valid": false, "error": ...}` shape with no `empty_header_indices` key. Fixes #11. --- docs/tool-guide.md | 3 +++ src/mcp_tools_lab/tools/csv_tool.py | 12 +++++++--- tests/test_csv_tool_empty_header.py | 36 +++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 tests/test_csv_tool_empty_header.py diff --git a/docs/tool-guide.md b/docs/tool-guide.md index a0b807d..ed54a58 100644 --- a/docs/tool-guide.md +++ b/docs/tool-guide.md @@ -36,6 +36,9 @@ Record numbering begins at 1 for the header; it counts parsed CSV records, not physical lines inside quoted values. Entirely blank lines are skipped by the parser. Empty input returns empty lists and zero rows. Malformed quoting returns `valid=False` and an error message. Extra cells are flagged but not counted as named columns. +Header cells that are empty or whitespace-only are reported by zero-based position in +`empty_header_indices`; a blank name is a warning, not an invalid document, and the +original names are kept in `columns` unchanged. ## hash_text(text, algorithm="sha256") diff --git a/src/mcp_tools_lab/tools/csv_tool.py b/src/mcp_tools_lab/tools/csv_tool.py index 4fd3e19..9235197 100644 --- a/src/mcp_tools_lab/tools/csv_tool.py +++ b/src/mcp_tools_lab/tools/csv_tool.py @@ -8,7 +8,10 @@ def inspect_csv(text: str) -> dict: """Report rows, empty cells, and ragged records (record 1 is the header). Missing counts include absent trailing cells and whitespace-only cells. - Entirely blank records are skipped by Python's CSV reader. + Entirely blank records are skipped by Python's CSV reader. Header cells that + are empty or whitespace-only are reported by zero-based position in + ``empty_header_indices``; the original names are still kept in ``columns``, + and a blank name is a warning, not an invalid document. """ try: rows = list(csv.reader(io.StringIO(text, newline=""), strict=True)) @@ -16,8 +19,10 @@ def inspect_csv(text: str) -> dict: return {"valid": False, "error": str(error)} if not rows: return {"valid": True, "columns": [], "row_count": 0, - "missing_values": [], "inconsistent_rows": []} + "missing_values": [], "inconsistent_rows": [], + "empty_header_indices": []} header, data = rows[0], rows[1:] + empty_header_indices = [index for index, name in enumerate(header) if not name.strip()] missing = [0] * len(header) inconsistent = [] for record, row in enumerate(data, start=2): @@ -30,4 +35,5 @@ def inspect_csv(text: str) -> dict: "missing_values": [ {"column": name, "index": index, "count": missing[index]} for index, name in enumerate(header) - ], "inconsistent_rows": inconsistent} + ], "inconsistent_rows": inconsistent, + "empty_header_indices": empty_header_indices} diff --git a/tests/test_csv_tool_empty_header.py b/tests/test_csv_tool_empty_header.py new file mode 100644 index 0000000..a755334 --- /dev/null +++ b/tests/test_csv_tool_empty_header.py @@ -0,0 +1,36 @@ +from mcp_tools_lab.tools.csv_tool import inspect_csv + + +def test_empty_header_indices_reports_positions(): + result = inspect_csv("name,,age\nAda,20,x\nBob,30,y") + assert result["empty_header_indices"] == [1] + + +def test_whitespace_only_header_reports_position(): + result = inspect_csv("name, ,age\nAda,20,x") + assert result["empty_header_indices"] == [1] + + +def test_empty_header_indices_lists_every_blank_column(): + result = inspect_csv("a,,c,,e\n1,2,3,4,5") + assert result["empty_header_indices"] == [1, 3] + + +def test_header_names_are_still_columns(): + result = inspect_csv("name,,age\nAda,20,x") + assert result["columns"] == ["name", "", "age"] + + +def test_empty_input_has_no_empty_header_indices(): + assert inspect_csv("")["empty_header_indices"] == [] + + +def test_blank_header_is_still_valid(): + assert inspect_csv("name,,age\nAda,20,x")["valid"] is True + + +def test_malformed_csv_does_not_report_empty_header_indices(): + # A document that will not parse has no header positions to name. + result = inspect_csv('name\n"unfinished') + assert result["valid"] is False + assert "empty_header_indices" not in result