diff --git a/packages/gds-framework/gds/parameters.py b/packages/gds-framework/gds/parameters.py index 8899fba..42f8d00 100644 --- a/packages/gds-framework/gds/parameters.py +++ b/packages/gds-framework/gds/parameters.py @@ -11,9 +11,9 @@ from __future__ import annotations -from typing import Any +from typing import Any, Self -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, model_validator from gds.types.typedef import TypeDef # noqa: TC001 @@ -32,6 +32,26 @@ class ParameterDef(BaseModel): description: str = "" bounds: tuple[Any, Any] | None = None + @model_validator(mode="after") + def _validate_bounds(self) -> Self: + """Validate that bounds are comparable and correctly ordered.""" + if self.bounds is None: + return self + low, high = self.bounds + try: + result = low <= high + except TypeError as e: + raise ValueError( + f"ParameterDef '{self.name}': bounds ({low!r}, {high!r}) " + f"are not comparable: {e}" + ) from None + if not result: + raise ValueError( + f"ParameterDef '{self.name}': lower bound {low!r} " + f"exceeds upper bound {high!r}" + ) + return self + def check_value(self, value: Any) -> bool: """Check if a value satisfies this parameter's type and constraints.""" if not self.typedef.check_value(value): diff --git a/packages/gds-framework/tests/test_v02_features.py b/packages/gds-framework/tests/test_v02_features.py index ee44d60..688f800 100644 --- a/packages/gds-framework/tests/test_v02_features.py +++ b/packages/gds-framework/tests/test_v02_features.py @@ -123,6 +123,26 @@ def test_check_value_no_bounds(self, float_type): p = ParameterDef(name="rate", typedef=float_type) assert p.check_value(999.0) is True + def test_bounds_inverted_raises(self, float_type): + """Inverted bounds (low > high) should fail at construction.""" + with pytest.raises(ValidationError, match="exceeds upper bound"): + ParameterDef(name="rate", typedef=float_type, bounds=(1.0, 0.0)) + + def test_bounds_non_comparable_raises(self, float_type): + """Non-comparable bounds should fail at construction.""" + with pytest.raises(ValidationError, match="not comparable"): + ParameterDef(name="rate", typedef=float_type, bounds=("a", 1)) + + def test_bounds_equal_is_valid(self, float_type): + """Equal bounds (low == high) should be allowed.""" + p = ParameterDef(name="rate", typedef=float_type, bounds=(0.5, 0.5)) + assert p.bounds == (0.5, 0.5) + + def test_bounds_none_is_valid(self, float_type): + """None bounds should be allowed (no validation).""" + p = ParameterDef(name="rate", typedef=float_type, bounds=None) + assert p.bounds is None + class TestParameterSchema: def test_empty_schema(self):