diff --git a/changes/2442-tobi-lipede-oodle.md b/changes/2442-tobi-lipede-oodle.md new file mode 100644 index 0000000000..0d947ef12f --- /dev/null +++ b/changes/2442-tobi-lipede-oodle.md @@ -0,0 +1 @@ +enable the Hypothesis plugin to generate a constrained float when the `multiple_of` argument is specified. \ No newline at end of file diff --git a/pydantic/_hypothesis_plugin.py b/pydantic/_hypothesis_plugin.py index 588e537646..345ecea118 100644 --- a/pydantic/_hypothesis_plugin.py +++ b/pydantic/_hypothesis_plugin.py @@ -276,6 +276,7 @@ def resolve_confloat(cls): # type: ignore[no-untyped-def] max_value = cls.le exclude_min = False exclude_max = False + if cls.gt is not None: assert min_value is None, 'Set `gt` or `ge`, but not both' min_value = cls.gt @@ -284,7 +285,21 @@ def resolve_confloat(cls): # type: ignore[no-untyped-def] assert max_value is None, 'Set `lt` or `le`, but not both' max_value = cls.lt exclude_max = True - return st.floats(min_value, max_value, exclude_min=exclude_min, exclude_max=exclude_max, allow_nan=False) + + if cls.multiple_of is None: + return st.floats(min_value, max_value, exclude_min=exclude_min, exclude_max=exclude_max, allow_nan=False) + + if min_value is not None: + min_value = math.ceil(min_value / cls.multiple_of) + if exclude_min: + min_value = min_value + 1 + if max_value is not None: + assert max_value >= cls.multiple_of, 'Cannot build model with max value smaller than multiple of' + max_value = math.floor(max_value / cls.multiple_of) + if exclude_max: + max_value = max_value - 1 + + return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of) @resolves(pydantic.ConstrainedInt) diff --git a/tests/test_hypothesis_plugin.py b/tests/test_hypothesis_plugin.py index bdd48fa382..facdcd6f1d 100644 --- a/tests/test_hypothesis_plugin.py +++ b/tests/test_hypothesis_plugin.py @@ -74,6 +74,8 @@ class ConstrainedNumbersModel(pydantic.BaseModel): conintmul: pydantic.conint(ge=10, le=100, multiple_of=7) confloatt: pydantic.confloat(gt=10, lt=100) confloate: pydantic.confloat(ge=10, le=100) + confloatemul: pydantic.confloat(ge=10, le=100, multiple_of=4.2) + confloattmul: pydantic.confloat(gt=10, lt=100, multiple_of=10) condecimalt: pydantic.condecimal(gt=10, lt=100) condecimale: pydantic.condecimal(ge=10, le=100)