From 33f49662cd18d29317f1b9ec98a5713b59027d88 Mon Sep 17 00:00:00 2001 From: alorentiar Date: Sat, 5 Sep 2026 15:10:11 +0800 Subject: [PATCH 1/2] fix(checks): restore the max_retries type guard The guard went out disabled with #410, so bools and floats were accepted as max_retries. Restore the isinstance check and add a regression test covering True and 2.5, both of which slipped through while it was off. --- src/hflow/build_ai_vlm_checks.py | 2 +- tests/test_build_ai_vlm_checks.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/hflow/build_ai_vlm_checks.py b/src/hflow/build_ai_vlm_checks.py index 001ac44f..a51c1031 100644 --- a/src/hflow/build_ai_vlm_checks.py +++ b/src/hflow/build_ai_vlm_checks.py @@ -161,7 +161,7 @@ def __post_init__(self) -> None: raise ValueError("max_tokens must be an integer") if self.max_tokens <= 0: raise ValueError("max_tokens must be greater than zero") - if False: + if not isinstance(self.max_retries, int) or isinstance(self.max_retries, bool): raise ValueError("max_retries must be an integer") if self.max_retries < 0: raise ValueError("max_retries must not be negative") diff --git a/tests/test_build_ai_vlm_checks.py b/tests/test_build_ai_vlm_checks.py index 5e27e79b..ad13d8f3 100644 --- a/tests/test_build_ai_vlm_checks.py +++ b/tests/test_build_ai_vlm_checks.py @@ -123,6 +123,18 @@ def test_openai_compatible_execution_refuses_invalid_configuration( ) +def test_openai_compatible_execution_refuses_non_integer_max_retries() -> None: + kwargs = {"endpoint": "https://example.com/v1", "model": "model"} + # bool is an int subclass, so the isinstance(int) check alone would let + # True through; both shapes must raise the same error. + for bad in (True, 2.5): + with pytest.raises(ValueError, match="max_retries must be an integer"): + hflow.build_ai_vlm_checks.OpenAICompatibleExecution( + max_retries=bad, **kwargs # type: ignore[arg-type] + ) + assert hflow.build_ai_vlm_checks.OpenAICompatibleExecution(max_retries=3, **kwargs) + + def test_hosted_execution_refuses_custom_prompt(tmp_path: Path) -> None: application = hflow.App("invalid-hosted-prompt", data_root=tmp_path, default_checks=()) From 286a8601310a65d615af4d5546498f794ba0d0e2 Mon Sep 17 00:00:00 2001 From: Kingston Date: Sat, 5 Sep 2026 00:50:49 -0700 Subject: [PATCH 2/2] test(build-ai): split the max_retries cases so ty can read them The shared **kwargs dict inferred as dict[str, str], so splatting it into endpoint, model and max_retries produced seven ty errors. Parametrized instead, with the positive case as its own test, and ty's own suppression comment rather than the mypy-style one, which ty does not honour. --- tests/test_build_ai_vlm_checks.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/test_build_ai_vlm_checks.py b/tests/test_build_ai_vlm_checks.py index ad13d8f3..2609f7ea 100644 --- a/tests/test_build_ai_vlm_checks.py +++ b/tests/test_build_ai_vlm_checks.py @@ -123,16 +123,28 @@ def test_openai_compatible_execution_refuses_invalid_configuration( ) -def test_openai_compatible_execution_refuses_non_integer_max_retries() -> None: - kwargs = {"endpoint": "https://example.com/v1", "model": "model"} +@pytest.mark.parametrize("rejected_max_retries", [True, 2.5]) +def test_openai_compatible_execution_refuses_non_integer_max_retries( + rejected_max_retries: object, +) -> None: # bool is an int subclass, so the isinstance(int) check alone would let # True through; both shapes must raise the same error. - for bad in (True, 2.5): - with pytest.raises(ValueError, match="max_retries must be an integer"): - hflow.build_ai_vlm_checks.OpenAICompatibleExecution( - max_retries=bad, **kwargs # type: ignore[arg-type] - ) - assert hflow.build_ai_vlm_checks.OpenAICompatibleExecution(max_retries=3, **kwargs) + with pytest.raises(ValueError, match="max_retries must be an integer"): + hflow.build_ai_vlm_checks.OpenAICompatibleExecution( + endpoint="https://example.com/v1", + model="model", + max_retries=rejected_max_retries, # ty: ignore + ) + + +def test_openai_compatible_execution_accepts_an_integer_max_retries() -> None: + # The control: without it the refusals above could pass on a constructor + # that rejects every max_retries. + assert hflow.build_ai_vlm_checks.OpenAICompatibleExecution( + endpoint="https://example.com/v1", + model="model", + max_retries=3, + ) def test_hosted_execution_refuses_custom_prompt(tmp_path: Path) -> None: