Skip to content

Commit 9fbb324

Browse files
committed
cowork-bot: batch convert no longer silently drops format-mismatched files - convert_batch returns BatchConversionResult with skipped[] (file + detected format), CLI reports each skip explicitly instead of a green 'complete' while dropping data; +1 regression test (149 pass), ruff clean
1 parent eee7094 commit 9fbb324

3 files changed

Lines changed: 69 additions & 10 deletions

File tree

src/datamorph/cli.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def batch_cmd(
118118
if csv_delimiter != ",":
119119
writer_kwargs["delimiter"] = csv_delimiter
120120

121-
results = convert_batch(
121+
batch = convert_batch(
122122
input_dir,
123123
output_dir,
124124
from_format,
@@ -127,11 +127,25 @@ def batch_cmd(
127127
recursive=recursive,
128128
**writer_kwargs,
129129
)
130+
results = batch.results
130131

131132
success = [r for r in results if not r.errors]
132133
failed = [r for r in results if r.errors]
133134

134135
console.print("\n[bold]Batch Conversion Complete[/bold]")
136+
137+
# Files that matched the pattern but were skipped due to format mismatch
138+
# are surfaced explicitly instead of vanishing silently.
139+
if batch.skipped:
140+
console.print(
141+
f" Skipped (format mismatch): {len(batch.skipped)}"
142+
" - did not match --from format"
143+
)
144+
for item in batch.skipped:
145+
err_console.print(
146+
f" [yellow]SKIPPED[/yellow] {item['file']} "
147+
f"(detected: {item['detected_format']}, expected: {from_format})"
148+
)
135149
console.print(f" Files: {len(success)} converted, {len(failed)} failed")
136150

137151
if failed:

src/datamorph/converters.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,21 @@ def _counting(stream: RowStream) -> RowStream:
600600
return result
601601

602602

603+
@dataclass
604+
class BatchConversionResult:
605+
"""Outcome of a batch conversion, including files that were skipped.
606+
607+
``skipped`` records every file that matched ``pattern`` but was NOT
608+
converted because its detected format differed from ``input_format``
609+
(or the format could not be detected at all). Surfacing these prevents
610+
the classic silent failure where a directory conversion quietly drops
611+
mislabeled or foreign-format files while reporting success.
612+
"""
613+
614+
results: list[ConversionResult] = field(default_factory=list)
615+
skipped: list[dict[str, str]] = field(default_factory=list)
616+
617+
603618
def convert_batch(
604619
input_dir: str | Path,
605620
output_dir: str | Path,
@@ -608,19 +623,26 @@ def convert_batch(
608623
pattern: str = "*",
609624
recursive: bool = False,
610625
**writer_kwargs: Any,
611-
) -> list[ConversionResult]:
626+
) -> BatchConversionResult:
612627
"""Convert all matching files in a directory."""
613628
input_dir = Path(input_dir)
614629
output_dir = Path(output_dir)
615630
output_dir.mkdir(parents=True, exist_ok=True)
616631

617632
glob_pattern = f"**/{pattern}" if recursive else pattern
618-
results: list[ConversionResult] = []
633+
batch = BatchConversionResult()
619634

620635
for input_path in sorted(input_dir.glob(glob_pattern)):
621636
if input_path.is_dir():
622637
continue
623-
if detect_format(str(input_path)) != input_format:
638+
detected = detect_format(str(input_path))
639+
if detected != input_format:
640+
batch.skipped.append(
641+
{
642+
"file": str(input_path),
643+
"detected_format": detected or "unknown",
644+
}
645+
)
624646
continue
625647

626648
# Preserve relative path structure
@@ -637,9 +659,9 @@ def convert_batch(
637659
output_format,
638660
**writer_kwargs,
639661
)
640-
results.append(result)
662+
batch.results.append(result)
641663

642-
return results
664+
return batch
643665

644666

645667
def _format_to_extension(fmt: str) -> str:

tests/test_converters.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import json
6+
from pathlib import Path
67

78
import pytest
89
import yaml
@@ -819,9 +820,10 @@ def test_batch_single_file(self, sample_csv, tmp_path):
819820
"json",
820821
pattern="test.csv",
821822
)
822-
assert len(results) >= 1
823-
assert not results[0].errors
824-
assert results[0].rows_written == 3
823+
assert len(results.results) >= 1
824+
assert results.skipped == []
825+
assert not results.results[0].errors
826+
assert results.results[0].rows_written == 3
825827
assert (output_dir / "test.json").exists()
826828

827829
def test_batch_no_matches(self, tmp_path):
@@ -835,7 +837,27 @@ def test_batch_no_matches(self, tmp_path):
835837
"json",
836838
pattern="*.csv",
837839
)
838-
assert results == []
840+
assert results.results == []
841+
assert results.skipped == []
842+
843+
844+
class TestBatchSkippedReporting:
845+
def test_format_mismatch_is_reported_not_silent(self, tmp_path):
846+
"""A file matching the pattern but with a different detected format is
847+
recorded in ``skipped`` instead of vanishing without a trace."""
848+
input_dir = tmp_path / "in"
849+
input_dir.mkdir()
850+
(input_dir / "data.csv").write_text("a,b\n1,2\n", encoding="utf-8")
851+
# Mislabeled: .csv extension but JSONL content -> detect_format sees jsonl? No:
852+
# detection is extension-based, so use a foreign extension instead.
853+
(input_dir / "notes.txt").write_text("hello", encoding="utf-8")
854+
output_dir = tmp_path / "out"
855+
results = convert_batch(str(input_dir), str(output_dir), "csv", "json")
856+
names = [Path(item["file"]).name for item in results.skipped]
857+
assert names == ["notes.txt"]
858+
assert results.skipped[0]["detected_format"] in ("txt", "unknown")
859+
# The real csv was still converted.
860+
assert [r.rows_written for r in results.results] == [1]
839861

840862

841863
# ── Type inference ────────────────────────────────────────────────────
@@ -993,3 +1015,4 @@ def test_scalar_bool_root_round_trip(self, tmp_path):
9931015
assert result.rows_read == 1
9941016
assert result.rows_written == 1
9951017
assert json.loads(out.read_text(encoding="utf-8")) == [{"data": True}]
1018+

0 commit comments

Comments
 (0)