diff --git a/polyfactory/value_generators/constrained_numbers.py b/polyfactory/value_generators/constrained_numbers.py index 23516ceb..4bdfdd8a 100644 --- a/polyfactory/value_generators/constrained_numbers.py +++ b/polyfactory/value_generators/constrained_numbers.py @@ -1,8 +1,9 @@ from __future__ import annotations from decimal import Decimal +from math import ceil, floor, frexp from sys import float_info -from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast +from typing import TYPE_CHECKING, Protocol, TypeVar, cast from polyfactory.exceptions import ParameterException from polyfactory.value_generators.primitives import create_random_decimal, create_random_float, create_random_integer @@ -99,8 +100,8 @@ def is_multiply_of_multiple_of_in_range( return False -def passes_pydantic_multiple_validator(value: T, multiple_of: T) -> bool: - """Determine whether a given value passes the pydantic multiple_of validation. +def is_almost_multiple_of(value: T, multiple_of: T) -> bool: + """Determine whether a given ``value`` is a close enough to a multiple of ``multiple_of``. :param value: A numeric value. :param multiple_of: Another numeric value. @@ -110,23 +111,37 @@ def passes_pydantic_multiple_validator(value: T, multiple_of: T) -> bool: """ if multiple_of == 0: return True - mod = float(value) / float(multiple_of) % 1 - return almost_equal_floats(mod, 0.0) or almost_equal_floats(mod, 1.0) + mod = value % multiple_of + return almost_equal_floats(float(mod), 0.0) or almost_equal_floats(float(abs(mod)), float(abs(multiple_of))) -def get_increment(t_type: type[T]) -> T: +def get_increment(value: T, t_type: type[T]) -> T: """Get a small increment base to add to constrained values, i.e. lt/gt entries. - :param t_type: A value of type T. + :param value: A value of type T. + :param t_type: The type of ``value``. :returns: An increment T. """ - values: dict[Any, Any] = { - int: 1, - float: float_info.epsilon, - Decimal: Decimal("0.001"), - } - return cast("T", values[t_type]) + # See https://github.com/python/mypy/issues/17045 for why the redundant casts are ignored. + if t_type == int: + return cast("T", 1) + if t_type == float: + # When ``value`` is large in magnitude, we need to choose an increment that is large enough + # to not be rounded away, but when ``value`` small in magnitude, we need to prevent the + # incerement from vanishing. ``float_info.epsilon`` is defined as the smallest delta that + # can be represented between 1.0 and the next largest number, but it's not sufficient for + # larger values. We instead open up the floating-point representation to grab the exponent + # and calculate our own increment. This can be replaced with ``math.ulp`` in Python 3.9 and + # later. + _, exp = frexp(value) + increment = float_info.radix ** (exp - float_info.mant_dig + 1) + return cast("T", max(increment, float_info.epsilon)) + if t_type == Decimal: + return cast("T", Decimal("0.001")) # type: ignore[redundant-cast] + + msg = f"invalid t_type: {t_type}" + raise AssertionError(msg) def get_value_or_none( @@ -147,14 +162,14 @@ def get_value_or_none( if ge is not None: minimum_value = ge elif gt is not None: - minimum_value = gt + get_increment(t_type) + minimum_value = gt + get_increment(gt, t_type) else: minimum_value = None if le is not None: maximum_value = le elif lt is not None: - maximum_value = lt - get_increment(t_type) + maximum_value = lt - get_increment(lt, t_type) else: maximum_value = None return minimum_value, maximum_value @@ -210,33 +225,36 @@ def get_constrained_number_range( return minimum, maximum -def generate_constrained_number( +def generate_constrained_multiple_of( random: Random, minimum: T | None, maximum: T | None, - multiple_of: T | None, - method: "NumberGeneratorProtocol[T]", + multiple_of: T, ) -> T: - """Generate a constrained number, output depends on the passed in callbacks. + """Generate a constrained multiple of ``multiple_of``. :param random: An instance of random. :param minimum: A minimum value. :param maximum: A maximum value. :param multiple_of: A multiple of value. - :param method: A function that generates numbers of type T. :returns: A value of type T. """ - if minimum is None or maximum is None: - return multiple_of if multiple_of is not None else method(random=random) - if multiple_of is None: - return method(random=random, minimum=minimum, maximum=maximum) - if multiple_of >= minimum: - return multiple_of - result = minimum - while not passes_pydantic_multiple_validator(result, multiple_of): - result = round(method(random=random, minimum=minimum, maximum=maximum) / multiple_of) * multiple_of - return result + + # Regardless of the type of ``multiple_of``, we can generate a valid multiple of it by + # multiplying it with any integer, which we call a multiplier. We will randomly generate the + # multiplier as a random integer, but we need to translate the original bounds, if any, to the + # correct bounds on the multiplier so that the resulting product will meet the original + # constraints. + + if multiple_of < 0: + minimum, maximum = maximum, minimum + + multiplier_min = ceil(minimum / multiple_of) if minimum is not None else None + multiplier_max = floor(maximum / multiple_of) if maximum is not None else None + multiplier = create_random_integer(random=random, minimum=multiplier_min, maximum=multiplier_max) + + return multiplier * multiple_of def handle_constrained_int( @@ -269,13 +287,11 @@ def handle_constrained_int( multiple_of=multiple_of, random=random, ) - return generate_constrained_number( - random=random, - minimum=minimum, - maximum=maximum, - multiple_of=multiple_of, - method=create_random_integer, - ) + + if multiple_of is None: + return create_random_integer(random=random, minimum=minimum, maximum=maximum) + + return generate_constrained_multiple_of(random=random, minimum=minimum, maximum=maximum, multiple_of=multiple_of) def handle_constrained_float( @@ -308,13 +324,10 @@ def handle_constrained_float( random=random, ) - return generate_constrained_number( - random=random, - minimum=minimum, - maximum=maximum, - multiple_of=multiple_of, - method=create_random_float, - ) + if multiple_of is None: + return create_random_float(random=random, minimum=minimum, maximum=maximum) + + return generate_constrained_multiple_of(random=random, minimum=minimum, maximum=maximum, multiple_of=multiple_of) def validate_max_digits( @@ -422,13 +435,15 @@ def handle_constrained_decimal( if max_digits is not None: validate_max_digits(max_digits=max_digits, minimum=minimum, decimal_places=decimal_places) - generated_decimal = generate_constrained_number( - random=random, - minimum=minimum, - maximum=maximum, - multiple_of=multiple_of, - method=create_random_decimal, - ) + if multiple_of is None: + generated_decimal = create_random_decimal(random=random, minimum=minimum, maximum=maximum) + else: + generated_decimal = generate_constrained_multiple_of( + random=random, + minimum=minimum, + maximum=maximum, + multiple_of=multiple_of, + ) if max_digits is not None or decimal_places is not None: return handle_decimal_length( diff --git a/tests/constraints/test_decimal_constraints.py b/tests/constraints/test_decimal_constraints.py index b6a76a3c..8e5b0d79 100644 --- a/tests/constraints/test_decimal_constraints.py +++ b/tests/constraints/test_decimal_constraints.py @@ -3,7 +3,7 @@ from typing import Optional, cast import pytest -from hypothesis import given +from hypothesis import assume, given from hypothesis.strategies import decimals, integers from pydantic import BaseModel, condecimal @@ -13,11 +13,24 @@ from polyfactory.value_generators.constrained_numbers import ( handle_constrained_decimal, handle_decimal_length, + is_almost_multiple_of, is_multiply_of_multiple_of_in_range, - passes_pydantic_multiple_validator, ) +def assume_max_digits(x: Decimal, max_digits: int) -> None: + """ + Signal to Hypothesis that ``x`` should have at most ``max_digits`` significant digits. + + This is different than the ``decimals()`` strategy function's ``places`` keyword argument, which + only counts the digits after the decimal point when the number is written without an exponent. + + E.g. 12.51 has 4 significant digits but 2 decimal places. + """ + + assume(len(x.as_tuple().digits) <= max_digits) + + def test_handle_constrained_decimal_without_constraints() -> None: result = handle_constrained_decimal( random=Random(), @@ -162,7 +175,7 @@ def test_handle_constrained_decimal_handles_multiple_of(multiple_of: Decimal) -> random=Random(), multiple_of=multiple_of, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_decimal( @@ -185,15 +198,17 @@ def test_handle_constrained_decimal_handles_multiple_of(multiple_of: Decimal) -> max_value=1000000000, ), ) -def test_handle_constrained_decimal_handles_multiple_of_with_lt(val1: Decimal, val2: Decimal) -> None: - multiple_of, max_value = sorted([val1, val2]) +def test_handle_constrained_decimal_handles_multiple_of_with_lt(max_value: Decimal, multiple_of: Decimal) -> None: if multiple_of != Decimal("0"): + assume_max_digits(max_value, 10) + assume_max_digits(multiple_of, 10) result = handle_constrained_decimal( random=Random(), multiple_of=multiple_of, lt=max_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert result < max_value + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_decimal( @@ -217,15 +232,17 @@ def test_handle_constrained_decimal_handles_multiple_of_with_lt(val1: Decimal, v max_value=1000000000, ), ) -def test_handle_constrained_decimal_handles_multiple_of_with_le(val1: Decimal, val2: Decimal) -> None: - multiple_of, max_value = sorted([val1, val2]) +def test_handle_constrained_decimal_handles_multiple_of_with_le(max_value: Decimal, multiple_of: Decimal) -> None: if multiple_of != Decimal("0"): + assume_max_digits(max_value, 10) + assume_max_digits(multiple_of, 10) result = handle_constrained_decimal( random=Random(), multiple_of=multiple_of, le=max_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert result <= max_value + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_decimal( @@ -249,15 +266,17 @@ def test_handle_constrained_decimal_handles_multiple_of_with_le(val1: Decimal, v max_value=1000000000, ), ) -def test_handle_constrained_decimal_handles_multiple_of_with_ge(val1: Decimal, val2: Decimal) -> None: - min_value, multiple_of = sorted([val1, val2]) +def test_handle_constrained_decimal_handles_multiple_of_with_ge(min_value: Decimal, multiple_of: Decimal) -> None: if multiple_of != Decimal("0"): + assume_max_digits(min_value, 10) + assume_max_digits(multiple_of, 10) result = handle_constrained_decimal( random=Random(), multiple_of=multiple_of, ge=min_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert min_value <= result + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_decimal( @@ -281,15 +300,17 @@ def test_handle_constrained_decimal_handles_multiple_of_with_ge(val1: Decimal, v max_value=1000000000, ), ) -def test_handle_constrained_decimal_handles_multiple_of_with_gt(val1: Decimal, val2: Decimal) -> None: - min_value, multiple_of = sorted([val1, val2]) +def test_handle_constrained_decimal_handles_multiple_of_with_gt(min_value: Decimal, multiple_of: Decimal) -> None: if multiple_of != Decimal("0"): + assume_max_digits(min_value, 10) + assume_max_digits(multiple_of, 10) result = handle_constrained_decimal( random=Random(), multiple_of=multiple_of, gt=min_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert min_value < result + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_decimal( @@ -322,21 +343,25 @@ def test_handle_constrained_decimal_handles_multiple_of_with_gt(val1: Decimal, v def test_handle_constrained_decimal_handles_multiple_of_with_ge_and_le( val1: Decimal, val2: Decimal, - val3: Decimal, + multiple_of: Decimal, ) -> None: - min_value, multiple_of, max_value = sorted([val1, val2, val3]) + min_value, max_value = sorted([val1, val2]) if multiple_of != Decimal("0") and is_multiply_of_multiple_of_in_range( minimum=min_value, maximum=max_value, multiple_of=multiple_of, ): + assume_max_digits(min_value, 10) + assume_max_digits(max_value, 10) + assume_max_digits(multiple_of, 10) result = handle_constrained_decimal( random=Random(), multiple_of=multiple_of, ge=min_value, le=max_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert min_value <= result <= max_value + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_decimal( diff --git a/tests/constraints/test_float_constraints.py b/tests/constraints/test_float_constraints.py index f7f4dae0..1529df47 100644 --- a/tests/constraints/test_float_constraints.py +++ b/tests/constraints/test_float_constraints.py @@ -1,17 +1,28 @@ +import math from random import Random import pytest -from hypothesis import given +from hypothesis import assume, given from hypothesis.strategies import floats from polyfactory.exceptions import ParameterException from polyfactory.value_generators.constrained_numbers import ( handle_constrained_float, + is_almost_multiple_of, is_multiply_of_multiple_of_in_range, - passes_pydantic_multiple_validator, ) +def assume_base2_exp_within(a: float, b: float, within: int) -> None: + """ + Signal to Hypothesis that ``a`` and ``b`` must be within ``within`` powers of 2 from each other. + """ + + _, exp_a = math.frexp(a) + _, exp_b = math.frexp(b) + assume(abs(exp_a - exp_b) <= within) + + def test_handle_constrained_float_without_constraints() -> None: result = handle_constrained_float( random=Random(), @@ -89,6 +100,7 @@ def test_handle_constrained_float_handles_lt(maximum: float) -> None: allow_infinity=False, min_value=-1000000000, max_value=1000000000, + width=32, ), ) def test_handle_constrained_float_handles_multiple_of(multiple_of: float) -> None: @@ -97,7 +109,7 @@ def test_handle_constrained_float_handles_multiple_of(multiple_of: float) -> Non random=Random(), multiple_of=multiple_of, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_float( @@ -118,17 +130,19 @@ def test_handle_constrained_float_handles_multiple_of(multiple_of: float) -> Non allow_infinity=False, min_value=-1000000000, max_value=1000000000, + width=32, ), ) -def test_handle_constrained_float_handles_multiple_of_with_lt(val1: float, val2: float) -> None: - multiple_of, max_value = sorted([val1, val2]) +def test_handle_constrained_float_handles_multiple_of_with_lt(max_value: float, multiple_of: float) -> None: if multiple_of != 0.0: + assume_base2_exp_within(max_value, multiple_of, 24) result = handle_constrained_float( random=Random(), multiple_of=multiple_of, lt=max_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert result < max_value + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_float( @@ -150,17 +164,19 @@ def test_handle_constrained_float_handles_multiple_of_with_lt(val1: float, val2: allow_infinity=False, min_value=-1000000000, max_value=1000000000, + width=32, ), ) -def test_handle_constrained_float_handles_multiple_of_with_le(val1: float, val2: float) -> None: - multiple_of, max_value = sorted([val1, val2]) +def test_handle_constrained_float_handles_multiple_of_with_le(max_value: float, multiple_of: float) -> None: if multiple_of != 0.0: + assume_base2_exp_within(max_value, multiple_of, 24) result = handle_constrained_float( random=Random(), multiple_of=multiple_of, le=max_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert result <= max_value + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_float( @@ -182,17 +198,19 @@ def test_handle_constrained_float_handles_multiple_of_with_le(val1: float, val2: allow_infinity=False, min_value=-1000000000, max_value=1000000000, + width=32, ), ) -def test_handle_constrained_float_handles_multiple_of_with_ge(val1: float, val2: float) -> None: - min_value, multiple_of = sorted([val1, val2]) +def test_handle_constrained_float_handles_multiple_of_with_ge(min_value: float, multiple_of: float) -> None: if multiple_of != 0.0: + assume_base2_exp_within(min_value, multiple_of, 24) result = handle_constrained_float( random=Random(), multiple_of=multiple_of, ge=min_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert min_value <= result + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_float( @@ -214,17 +232,19 @@ def test_handle_constrained_float_handles_multiple_of_with_ge(val1: float, val2: allow_infinity=False, min_value=-1000000000, max_value=1000000000, + width=32, ), ) -def test_handle_constrained_float_handles_multiple_of_with_gt(val1: float, val2: float) -> None: - min_value, multiple_of = sorted([val1, val2]) +def test_handle_constrained_float_handles_multiple_of_with_gt(min_value: float, multiple_of: float) -> None: if multiple_of != 0.0: + assume_base2_exp_within(min_value, multiple_of, 24) result = handle_constrained_float( random=Random(), multiple_of=multiple_of, gt=min_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert min_value < result + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_float( @@ -253,22 +273,30 @@ def test_handle_constrained_float_handles_multiple_of_with_gt(val1: float, val2: allow_infinity=False, min_value=-1000000000, max_value=1000000000, + width=32, ), ) -def test_handle_constrained_float_handles_multiple_of_with_ge_and_le(val1: float, val2: float, val3: float) -> None: - min_value, multiple_of, max_value = sorted([val1, val2, val3]) +def test_handle_constrained_float_handles_multiple_of_with_ge_and_le( + val1: float, + val2: float, + multiple_of: float, +) -> None: + min_value, max_value = sorted([val1, val2]) if multiple_of != 0.0 and is_multiply_of_multiple_of_in_range( minimum=min_value, maximum=max_value, multiple_of=multiple_of, ): + assume_base2_exp_within(min_value, multiple_of, 24) + assume_base2_exp_within(max_value, multiple_of, 24) result = handle_constrained_float( random=Random(), multiple_of=multiple_of, ge=min_value, lt=max_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert min_value <= result <= max_value + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_float( diff --git a/tests/constraints/test_int_constraints.py b/tests/constraints/test_int_constraints.py index 67edb306..51fbff64 100644 --- a/tests/constraints/test_int_constraints.py +++ b/tests/constraints/test_int_constraints.py @@ -7,8 +7,8 @@ from polyfactory.exceptions import ParameterException from polyfactory.value_generators.constrained_numbers import ( handle_constrained_int, + is_almost_multiple_of, is_multiply_of_multiple_of_in_range, - passes_pydantic_multiple_validator, ) @@ -78,7 +78,7 @@ def test_handle_constrained_int_handles_multiple_of(multiple_of: int) -> None: random=Random(), multiple_of=multiple_of, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_int( @@ -91,15 +91,15 @@ def test_handle_constrained_int_handles_multiple_of(multiple_of: int) -> None: integers(min_value=-1000000000, max_value=1000000000), integers(min_value=-1000000000, max_value=1000000000), ) -def test_handle_constrained_int_handles_multiple_of_with_lt(val1: int, val2: int) -> None: - multiple_of, max_value = sorted([val1, val2]) +def test_handle_constrained_int_handles_multiple_of_with_lt(max_value: int, multiple_of: int) -> None: if multiple_of != 0: result = handle_constrained_int( random=Random(), multiple_of=multiple_of, lt=max_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert result < max_value + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_int( @@ -113,15 +113,15 @@ def test_handle_constrained_int_handles_multiple_of_with_lt(val1: int, val2: int integers(min_value=-1000000000, max_value=1000000000), integers(min_value=-1000000000, max_value=1000000000), ) -def test_handle_constrained_int_handles_multiple_of_with_le(val1: int, val2: int) -> None: - multiple_of, max_value = sorted([val1, val2]) +def test_handle_constrained_int_handles_multiple_of_with_le(max_value: int, multiple_of: int) -> None: if multiple_of != 0: result = handle_constrained_int( random=Random(), multiple_of=multiple_of, le=max_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert result <= max_value + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_int( @@ -135,15 +135,15 @@ def test_handle_constrained_int_handles_multiple_of_with_le(val1: int, val2: int integers(min_value=-1000000000, max_value=1000000000), integers(min_value=-1000000000, max_value=1000000000), ) -def test_handle_constrained_int_handles_multiple_of_with_ge(val1: int, val2: int) -> None: - min_value, multiple_of = sorted([val1, val2]) +def test_handle_constrained_int_handles_multiple_of_with_ge(min_value: int, multiple_of: int) -> None: if multiple_of != 0: result = handle_constrained_int( random=Random(), multiple_of=multiple_of, ge=min_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert min_value <= result + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_int( @@ -157,15 +157,15 @@ def test_handle_constrained_int_handles_multiple_of_with_ge(val1: int, val2: int integers(min_value=-1000000000, max_value=1000000000), integers(min_value=-1000000000, max_value=1000000000), ) -def test_handle_constrained_int_handles_multiple_of_with_gt(val1: int, val2: int) -> None: - min_value, multiple_of = sorted([val1, val2]) +def test_handle_constrained_int_handles_multiple_of_with_gt(min_value: int, multiple_of: int) -> None: if multiple_of != 0: result = handle_constrained_int( random=Random(), multiple_of=multiple_of, gt=min_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert min_value < result + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_int( @@ -180,8 +180,8 @@ def test_handle_constrained_int_handles_multiple_of_with_gt(val1: int, val2: int integers(min_value=-1000000000, max_value=1000000000), integers(min_value=-1000000000, max_value=1000000000), ) -def test_handle_constrained_int_handles_multiple_of_with_ge_and_le(val1: int, val2: int, val3: int) -> None: - min_value, multiple_of, max_value = sorted([val1, val2, val3]) +def test_handle_constrained_int_handles_multiple_of_with_ge_and_le(val1: int, val2: int, multiple_of: int) -> None: + min_value, max_value = sorted([val1, val2]) if multiple_of != 0 and is_multiply_of_multiple_of_in_range( minimum=min_value, maximum=max_value, @@ -193,7 +193,8 @@ def test_handle_constrained_int_handles_multiple_of_with_ge_and_le(val1: int, va ge=min_value, le=max_value, ) - assert passes_pydantic_multiple_validator(result, multiple_of) + assert min_value <= result <= max_value + assert is_almost_multiple_of(result, multiple_of) else: with pytest.raises(ParameterException): handle_constrained_int( diff --git a/tests/test_number_generation.py b/tests/test_number_generation.py index 4800ecc7..1de0978a 100644 --- a/tests/test_number_generation.py +++ b/tests/test_number_generation.py @@ -1,30 +1,7 @@ -from random import Random - -import pytest - from polyfactory.value_generators.constrained_numbers import ( - generate_constrained_number, - passes_pydantic_multiple_validator, -) -from polyfactory.value_generators.primitives import create_random_float - - -@pytest.mark.parametrize( - ("maximum", "minimum", "multiple_of"), - ((100, 2, 8), (-100, -187, -10), (7.55, 0.13, 0.0123)), + is_almost_multiple_of, ) -def test_generate_constrained_number(maximum: float, minimum: float, multiple_of: float) -> None: - assert passes_pydantic_multiple_validator( - multiple_of=multiple_of, - value=generate_constrained_number( - random=Random(), - minimum=minimum, - maximum=maximum, - multiple_of=multiple_of, - method=create_random_float, - ), - ) -def test_passes_pydantic_multiple_validator_handles_zero_multiplier() -> None: - assert passes_pydantic_multiple_validator(1.0, 0) +def test_is_close_enough_multiple_of_handles_zero_multiplier() -> None: + assert is_almost_multiple_of(1.0, 0)