aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/value.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/cru-py/cru/value.py')
-rw-r--r--tools/cru-py/cru/value.py201
1 files changed, 78 insertions, 123 deletions
diff --git a/tools/cru-py/cru/value.py b/tools/cru-py/cru/value.py
index 189f44f..4096362 100644
--- a/tools/cru-py/cru/value.py
+++ b/tools/cru-py/cru/value.py
@@ -5,23 +5,23 @@ import secrets
import string
import uuid
from abc import abstractmethod, ABCMeta
-from collections.abc import Mapping, Callable
-from typing import Any, ClassVar, Literal, TypeVar, Generic, ParamSpec
+from collections.abc import Callable
+from typing import Any, ClassVar, TypeVar, Generic
-from .error import CruInternalError, CruException
+from ._error import CruException
-def _str_case_in(s: str, case: bool, l: list[str]) -> bool:
+def _str_case_in(s: str, case: bool, str_list: list[str]) -> bool:
if case:
- return s in l
+ return s in str_list
else:
- return s.lower() in [s.lower() for s in l]
+ return s.lower() in [s.lower() for s in str_list]
-T = TypeVar("T")
+_T = TypeVar("_T")
-class CruValueError(CruException):
+class CruValueTypeError(CruException):
def __init__(
self,
message: str,
@@ -47,17 +47,8 @@ class CruValueError(CruException):
return self._value_type
-class CruValueValidationError(CruValueError):
- pass
-
-
-class CruValueStringConversionError(CruValueError):
- pass
-
-
-# TODO: Continue here tomorrow!
-class ValueType(Generic[T], metaclass=ABCMeta):
- def __init__(self, name: str, _type: type[T]) -> None:
+class ValueType(Generic[_T], metaclass=ABCMeta):
+ def __init__(self, name: str, _type: type[_T]) -> None:
self._name = name
self._type = _type
@@ -66,67 +57,61 @@ class ValueType(Generic[T], metaclass=ABCMeta):
return self._name
@property
- def type(self) -> type[T]:
+ def type(self) -> type[_T]:
return self._type
- def check_value_type(self, value: Any) -> bool:
- return isinstance(value, self.type)
+ def check_value_type(self, value: Any) -> None:
+ if not isinstance(value, self.type):
+ raise CruValueTypeError("Type of value is wrong.", value, self)
- def _do_check_value(self, value: Any) -> T:
+ def _do_check_value(self, value: Any) -> _T:
return value
- def check_value(self, value: Any) -> T:
- if not isinstance(value, self.type):
- raise CruValueValidationError("Value type is wrong.", value, self)
+ def check_value(self, value: Any) -> _T:
+ self.check_value_type(value)
return self._do_check_value(value)
- def _do_check_str_format(self, s: str) -> bool | tuple[bool, str]:
+ @abstractmethod
+ def _do_check_str_format(self, s: str) -> None:
raise NotImplementedError()
def check_str_format(self, s: str) -> None:
- ok, err = self._do_check_str_format(s)
- if ok is None:
- raise CruInternalLogicError("_do_check_str_format should not return None.")
- if ok:
- return
- if err is None:
- err = "Invalid value str format."
- raise ValueStringConvertionError(err, s, value_type=self)
+ if not isinstance(s, str):
+ raise CruValueTypeError("Try to check format on a non-str.", s, self)
+ self._do_check_str_format(s)
@abstractmethod
- def _do_convert_value_to_str(self, value: T) -> str:
+ def _do_convert_value_to_str(self, value: _T) -> str:
raise NotImplementedError()
- def convert_value_to_str(self, value: T) -> str:
+ def convert_value_to_str(self, value: _T) -> str:
self.check_value(value)
return self._do_convert_value_to_str(value)
@abstractmethod
- def _do_convert_str_to_value(self, s: str) -> T:
+ def _do_convert_str_to_value(self, s: str) -> _T:
raise NotImplementedError()
- def convert_str_to_value(self, s: str) -> T:
+ def convert_str_to_value(self, s: str) -> _T:
self.check_str_format(s)
return self._do_convert_str_to_value(s)
- def check_value_or_try_convert_from_str(self, value_or_str: Any) -> T:
+ def check_value_or_try_convert_from_str(self, value_or_str: Any) -> _T:
try:
return self.check_value(value_or_str)
- except ValidationError as e:
+ except CruValueTypeError:
if isinstance(value_or_str, str):
return self.convert_str_to_value(value_or_str)
else:
- raise ValidationError(
- "Value is not valid and is not a str.", value_or_str, self, inner=e
- )
+ raise
class TextValueType(ValueType[str]):
def __init__(self) -> None:
- super().__init__("text")
+ super().__init__("text", str)
- def _do_check_str_format(self, s):
- return True
+ def _do_check_str_format(self, _s):
+ return
def _do_convert_value_to_str(self, value):
return value
@@ -138,14 +123,13 @@ class TextValueType(ValueType[str]):
class IntegerValueType(ValueType[int]):
def __init__(self) -> None:
- super().__init__("integer")
+ super().__init__("integer", int)
def _do_check_str_format(self, s):
try:
int(s)
- return True
- except ValueError:
- return False
+ except ValueError as e:
+ raise CruValueTypeError("Invalid integer format.", s, self) from e
def _do_convert_value_to_str(self, value):
return str(value)
@@ -156,14 +140,13 @@ class IntegerValueType(ValueType[int]):
class FloatValueType(ValueType[float]):
def __init__(self) -> None:
- super().__init__("float")
+ super().__init__("float", float)
def _do_check_str_format(self, s):
try:
float(s)
- return True
- except ValueError:
- return False
+ except ValueError as e:
+ raise CruValueTypeError("Invalid float format.", s, self) from e
def _do_convert_value_to_str(self, value):
return str(value)
@@ -183,7 +166,7 @@ class BooleanValueType(ValueType[bool]):
true_list: None | list[str] = None,
false_list: None | list[str] = None,
) -> None:
- super().__init__("boolean")
+ super().__init__("boolean", bool)
self._case_sensitive = case_sensitive
self._valid_true_strs: list[str] = (
true_list or BooleanValueType.DEFAULT_TRUE_LIST
@@ -209,15 +192,11 @@ class BooleanValueType(ValueType[bool]):
return self._valid_true_strs + self._valid_false_strs
def _do_check_str_format(self, s):
- if _str_case_in(s, self.case_sensitive, self.valid_boolean_strs):
- return True
- return (
- False,
- f"Not a valid boolean string ({ValueType.case_sensitive_to_str(self.case_sensitive)}). Valid string of true: {' '.join(self._valid_true_strs)}. Valid string of false: {' '.join(self._valid_false_strs)}. All is case insensitive.",
- )
+ if not _str_case_in(s, self.case_sensitive, self.valid_boolean_strs):
+ raise CruValueTypeError("Invalid boolean format.", s, self)
def _do_convert_value_to_str(self, value):
- return "True" if value else "False"
+ return self._valid_true_strs[0] if value else self._valid_false_strs[0]
def _do_convert_str_to_value(self, s):
return _str_case_in(s, self.case_sensitive, self._valid_true_strs)
@@ -225,9 +204,7 @@ class BooleanValueType(ValueType[bool]):
class EnumValueType(ValueType[str]):
def __init__(self, valid_values: list[str], /, case_sensitive=False) -> None:
- s = " | ".join([f'"{v}"' for v in valid_values])
- self._valid_value_str = f"[ {s} ]"
- super().__init__(f"enum{self._valid_value_str}")
+ super().__init__(f"enum({'|'.join(valid_values)})", str)
self._case_sensitive = case_sensitive
self._valid_values = valid_values
@@ -240,16 +217,11 @@ class EnumValueType(ValueType[str]):
return self._valid_values
def _do_check_value(self, value):
- ok, err = self._do_check_str_format(value)
- return ok, (value if ok else err)
+ self._do_check_str_format(value)
def _do_check_str_format(self, s):
- if _str_case_in(s, self.case_sensitive, self.valid_values):
- return True
- return (
- False,
- f"Value is not in valid values ({ValueType.case_sensitive_to_str(self.case_sensitive)}): {self._valid_value_str}",
- )
+ if not _str_case_in(s, self.case_sensitive, self.valid_values):
+ raise CruValueTypeError("Invalid enum value", s, self)
def _do_convert_value_to_str(self, value):
return value
@@ -262,69 +234,52 @@ TEXT_VALUE_TYPE = TextValueType()
INTEGER_VALUE_TYPE = IntegerValueType()
BOOLEAN_VALUE_TYPE = BooleanValueType()
-P = ParamSpec("P")
+class ValueGeneratorBase(Generic[_T], metaclass=ABCMeta):
+ @abstractmethod
+ def generate(self) -> _T:
+ raise NotImplementedError()
-class ValueGenerator(Generic[T, P]):
- INTERACTIVE_KEY: ClassVar[Literal["interactive"]] = "interactive"
+ def __call__(self) -> _T:
+ return self.generate()
- def __init__(
- self, f: Callable[P, T], /, attributes: None | Mapping[str, Any] = None
- ) -> None:
- self._f = f
- self._attributes = attributes or {}
- @property
- def f(self) -> Callable[P, T]:
- return self._f
+class ValueGenerator(ValueGeneratorBase[_T]):
+ def __init__(self, generate_func: Callable[[], _T]) -> None:
+ self._generate_func = generate_func
@property
- def attributes(self) -> Mapping[str, Any]:
- return self._attributes
+ def generate_func(self) -> Callable[[], _T]:
+ return self._generate_func
- def generate(self, *args, **kwargs) -> T:
- return self._f(*args, **kwargs)
+ def generate(self) -> _T:
+ return self._generate_func()
- def __call__(self, *args, **kwargs):
- return self._f(*args, **kwargs)
- @property
- def interactive(self) -> bool:
- return self._attributes.get(ValueGenerator.INTERACTIVE_KEY, False)
-
- @staticmethod
- def create_interactive(
- f: Callable[P, T],
- interactive: bool = True,
- /,
- attributes: None | Mapping[str, Any] = None,
- ) -> "ValueGenerator[T, P]":
- return ValueGenerator(
- f, dict({ValueGenerator.INTERACTIVE_KEY: interactive}, **(attributes or {}))
- )
-
-
-class UuidValueGenerator(ValueGenerator[str, []]):
- def __init__(self) -> None:
- super().__init__(lambda: str(uuid.uuid4()))
+class UuidValueGenerator(ValueGeneratorBase[str]):
+ def generate(self):
+ return str(uuid.uuid4())
-class RandomStringValueGenerator(ValueGenerator[str, []]):
- @staticmethod
- def _create_generate_ramdom_func(length: int, secure: bool) -> Callable[str, []]:
- random_choice = secrets.choice if secure else random.choice
- def generate_random_string():
- characters = string.ascii_letters + string.digits
- random_string = "".join(random_choice(characters) for _ in range(length))
- return random_string
+class RandomStringValueGenerator(ValueGeneratorBase[str]):
+ def __init__(self, length: int, secure: bool) -> None:
+ self._length = length
+ self._secure = secure
- return generate_random_string
+ @property
+ def length(self) -> int:
+ return self._length
- def __init__(self, length: int, secure: bool) -> None:
- super().__init__(
- RandomStringValueGenerator._create_generate_ramdom_func(length, secure)
- )
+ @property
+ def secure(self) -> bool:
+ return self._secure
+
+ def generate(self):
+ random_func = secrets.choice if self._secure else random.choice
+ characters = string.ascii_letters + string.digits
+ random_string = "".join(random_func(characters) for _ in range(self._length))
+ return random_string
UUID_VALUE_GENERATOR = UuidValueGenerator()