# ruff: noqa: SLF001
import re
-from typing import Any, Dict, Type
+from typing import Any, Dict, Type, Union
from knot_resolver.utils.compat.typing import Pattern
from knot_resolver.utils.modeling import BaseValueType
return {"type": "integer"}
+class FloatBase(BaseValueType):
+ """
+ Base class to work with float value.
+ """
+
+ _orig_value: Union[float, int]
+ _value: float
+
+ def __init__(self, source_value: Any, object_path: str = "/") -> None:
+ if isinstance(source_value, (float, int)) and not isinstance(source_value, bool):
+ self._orig_value = source_value
+ self._value = float(source_value)
+ else:
+ raise ValueError(
+ f"Unexpected value for '{type(self)}'."
+ f" Expected float, got '{source_value}' with type '{type(source_value)}'",
+ object_path,
+ )
+
+ def __int__(self) -> int:
+ return int(self._value)
+
+ def __float__(self) -> float:
+ return self._value
+
+ def __str__(self) -> str:
+ return str(self._value)
+
+ def __repr__(self) -> str:
+ return f'{type(self).__name__}("{self._value}")'
+
+ def __eq__(self, o: object) -> bool:
+ return isinstance(o, FloatBase) and o._value == self._value
+
+ def serialize(self) -> Any:
+ return self._orig_value
+
+ @classmethod
+ def json_schema(cls: Type["FloatBase"]) -> Dict[Any, Any]:
+ return {"type": "number"}
+
+
class StrBase(BaseValueType):
"""
Base class to work with string value.
return typ
+class FloatRangeBase(FloatBase):
+ """
+ Base class to work with float value in range.
+ Just inherit the class and set the values for '_min' and '_max'.
+
+ class FloatNonNegative(IntRangeBase):
+ _min: float = 0.0
+ """
+
+ _min: float
+ _max: float
+
+ def __init__(self, source_value: Any, object_path: str = "/") -> None:
+ super().__init__(source_value, object_path)
+ if hasattr(self, "_min") and (self._value < self._min):
+ raise ValueError(f"value {self._value} is lower than the minimum {self._min}.", object_path)
+ if hasattr(self, "_max") and (self._value > self._max):
+ raise ValueError(f"value {self._value} is higher than the maximum {self._max}", object_path)
+
+ @classmethod
+ def json_schema(cls: Type["FloatRangeBase"]) -> Dict[Any, Any]:
+ typ: Dict[str, Any] = {"type": "number"}
+ if hasattr(cls, "_min"):
+ typ["minimum"] = cls._min
+ if hasattr(cls, "_max"):
+ typ["maximum"] = cls._max
+ return typ
+
+
class PatternBase(StrBase):
"""
Base class to work with string value that match regex pattern.
import re
from typing import Any, Dict, Optional, Type, Union
-from knot_resolver.datamodel.types.base_types import IntRangeBase, PatternBase, StrBase, StringLengthBase, UnitBase
+from knot_resolver.datamodel.types.base_types import (
+ FloatRangeBase,
+ IntRangeBase,
+ PatternBase,
+ StrBase,
+ StringLengthBase,
+ UnitBase,
+)
from knot_resolver.utils.modeling import BaseValueType
raise ValueError(f"invalid port number {port}") from e
+class FloatNonNegative(FloatRangeBase):
+ _min: float = 0.0
+
+
class SizeUnit(UnitBase):
_units = {"B": 1, "K": 1024, "M": 1024**2, "G": 1024**3}
from pytest import raises
from knot_resolver import KresBaseException
-from knot_resolver.datamodel.types.base_types import IntRangeBase, StringLengthBase
+from knot_resolver.datamodel.types.base_types import FloatRangeBase, IntRangeBase, StringLengthBase
@pytest.mark.parametrize("min,max", [(0, None), (None, 0), (1, 65535), (-65535, -1)])
Test(inval)
+@pytest.mark.parametrize("min,max", [(0.0, None), (None, 0.0), (1.0, 65535.0), (-65535.0, -1.0)])
+def test_float_range_base(min: Optional[float], max: Optional[float]):
+ class Test(FloatRangeBase):
+ if min:
+ _min = min
+ if max:
+ _max = max
+
+ if min:
+ assert float(Test(min)) == min
+ if max:
+ assert float(Test(max)) == max
+
+ rmin = min if min else sys.float_info.min - 1.0
+ rmax = max if max else sys.float_info.max
+
+ n = 100
+ vals: List[float] = [random.uniform(rmin, rmax) for _ in range(n)]
+ assert [str(Test(val)) == f"{val}" for val in vals]
+
+ invals: List[float] = []
+ invals.extend([random.uniform(rmax + 1.0, sys.float_info.max) for _ in range(n % 2)] if max else [])
+ invals.extend([random.uniform(sys.float_info.min - 1.0, rmin - 1.0) for _ in range(n % 2)] if max else [])
+
+ for inval in invals:
+ with raises(KresBaseException):
+ Test(inval)
+
+
@pytest.mark.parametrize("min,max", [(10, None), (None, 10), (2, 32)])
def test_str_bytes_length_base(min: Optional[int], max: Optional[int]):
class Test(StringLengthBase):