Skip to content

Commit d378485

Browse files
jawwad-aliclaude
andauthored
fix(workflows): reject infinite number-input default instead of raising OverflowError (#3199)
WorkflowEngine._coerce_input normalizes a whole-valued number to int via int(value). For an infinite float (e.g. a 'type: number' input with YAML 'default: .inf') int(inf) raises OverflowError, which is not in the except (ValueError, TypeError) tuple. validate_workflow eager-coerces declared defaults and is documented to RETURN a list of errors, but it only catches ValueError -- so the OverflowError escaped and validate_workflow raised instead of reporting, breaking its contract. (NaN already surfaced cleanly because int(nan) raises ValueError.) Add OverflowError to the except tuple so an infinite default surfaces as the same clean 'expected a number' ValueError as NaN, consistent with the function's existing fail-fast-on-authoring-mistakes design. Finite values (5.0 -> 5, 3.5 -> 3.5) are unaffected. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 96f73d1 commit d378485

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

src/specify_cli/workflows/engine.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,12 @@ def _coerce_input(
10101010
value = float(value)
10111011
if value == int(value):
10121012
value = int(value)
1013-
except (ValueError, TypeError):
1013+
except (ValueError, TypeError, OverflowError):
1014+
# OverflowError: `int(value)` raises it for an infinite float
1015+
# (e.g. a `default: .inf` authoring mistake), which would
1016+
# otherwise escape validate_workflow's `except ValueError` and
1017+
# break its "return errors, never raise" contract. Surface it as
1018+
# the same clean "expected a number" error as NaN does.
10141019
msg = f"Input {name!r} expected a number, got {value!r}."
10151020
raise ValueError(msg) from None
10161021
elif input_type == "boolean":

tests/test_workflows.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2846,6 +2846,47 @@ def test_validate_workflow_rejects_bool_default_for_number_type(self):
28462846
errors = validate_workflow(definition)
28472847
assert any("invalid default" in e for e in errors), errors
28482848

2849+
def test_coerce_number_input_rejects_infinity_cleanly(self):
2850+
"""An infinite float must surface as a clean ValueError (like NaN), not
2851+
let ``int(inf)``'s OverflowError escape: ``int()`` of an infinity raises
2852+
OverflowError, which is not ValueError/TypeError.
2853+
"""
2854+
from specify_cli.workflows.engine import WorkflowEngine
2855+
2856+
for value in (float("inf"), float("-inf"), "inf", "Infinity", "-inf"):
2857+
with pytest.raises(ValueError, match="expected a number"):
2858+
WorkflowEngine._coerce_input("count", value, {"type": "number"})
2859+
# Finite values still coerce (whole floats normalize to int).
2860+
assert WorkflowEngine._coerce_input("count", 5.0, {"type": "number"}) == 5
2861+
assert WorkflowEngine._coerce_input("count", 3.5, {"type": "number"}) == 3.5
2862+
2863+
def test_validate_workflow_rejects_infinite_default_for_number_type(self):
2864+
"""``type: number`` with an infinite default (YAML ``.inf``) must be
2865+
reported as an error, not raise. ``int(inf)`` raises OverflowError during
2866+
coercion, which previously escaped validate_workflow's ValueError handler
2867+
and broke its "return a list of errors" contract.
2868+
"""
2869+
from specify_cli.workflows.engine import WorkflowDefinition, validate_workflow
2870+
2871+
definition = WorkflowDefinition.from_string("""
2872+
schema_version: "1.0"
2873+
workflow:
2874+
id: "inf-as-number"
2875+
name: "Inf As Number"
2876+
version: "1.0.0"
2877+
inputs:
2878+
count:
2879+
type: number
2880+
default: .inf
2881+
steps:
2882+
- id: noop
2883+
type: gate
2884+
message: "noop"
2885+
options: [approve]
2886+
""")
2887+
errors = validate_workflow(definition)
2888+
assert any("invalid default" in e for e in errors), errors
2889+
28492890
def test_validate_workflow_rejects_non_string_default_for_string_type(self):
28502891
"""``type: string`` must require an actual string — a numeric YAML
28512892
default like ``5`` would otherwise slip through unvalidated.

0 commit comments

Comments
 (0)