Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions faircode/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@ def load_manifest(path) -> Manifest:
path = Path(path)
with open(path) as f:
data = yaml.safe_load(f)
if not isinstance(data, dict):
# An empty file parses to None, and a file passed by mistake (a CSV,
# say) to a string; both used to die inside data.get() (#1177).
raise ValueError(
f"{path}: the manifest must be a mapping of keys to values, "
f"got {type(data).__name__}"
)
return Manifest.from_dict(data, path)


Expand Down
32 changes: 32 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,38 @@ def raise_value_error(**kwargs):
captured = capsys.readouterr()
assert "error: bad_audit/audit.yaml: 'target'" in captured.err

def test_cli_benchmark_empty_manifest_returns_2_with_clean_error(tmp_path, capsys):
"""#1177: an empty manifest used to raise AttributeError."""
pytest.importorskip("sklearn", reason="benchmark extra required")
pytest.importorskip("fairlearn", reason="benchmark extra required")
pytest.importorskip("yaml", reason="benchmark extra required")

empty = tmp_path / "empty.yaml"
empty.write_text("")
exit_code = main(["benchmark", str(empty), "--no-plots"])

assert exit_code == 2
captured = capsys.readouterr()
assert f"error: {empty}:" in captured.err
assert "must be a mapping" in captured.err
assert "Traceback" not in captured.err


def test_cli_benchmark_non_mapping_manifest_returns_2_with_clean_error(tmp_path, capsys):
"""#1177: a CSV passed by mistake used to raise AttributeError."""
pytest.importorskip("sklearn", reason="benchmark extra required")
pytest.importorskip("fairlearn", reason="benchmark extra required")
pytest.importorskip("yaml", reason="benchmark extra required")

path = tmp_path / "tiny.csv"
path.write_text("name,title\ntoy,Toy\n")
exit_code = main(["benchmark", str(path), "--no-plots"])

assert exit_code == 2
captured = capsys.readouterr()
assert "must be a mapping" in captured.err
assert "Traceback" not in captured.err


@pytest.mark.skipif(not SMALL_AUDIT.is_file(), reason="German Credit Lending fixture not found")
@pytest.mark.slow # trains models end to end (about 15 s); `make test-fast` skips it (#910)
Expand Down
13 changes: 13 additions & 0 deletions tests/test_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,19 @@ def test_empty_protected_attributes_raises(tmp_path):
load_manifest(path)


def test_an_empty_manifest_raises_a_mapping_error(tmp_path):
path = tmp_path / "audit.yaml"
path.write_text("")
with pytest.raises(ValueError, match="must be a mapping"):
load_manifest(path)


def test_a_manifest_that_is_not_a_mapping_raises(tmp_path):
path = tmp_path / "audit.yaml"
path.write_text("a,b\n1,2\n")
with pytest.raises(ValueError, match="must be a mapping"):
load_manifest(path)

def test_unknown_target_method_fails_loudly():
spec = TargetSpec(column="x", method="not_a_real_method")
df = pd.DataFrame({"x": [1, 2, 3]})
Expand Down
Loading