diff options
Diffstat (limited to 'tools/cru-py/cru/value.py')
-rw-r--r-- | tools/cru-py/cru/value.py | 157 |
1 files changed, 89 insertions, 68 deletions
diff --git a/tools/cru-py/cru/value.py b/tools/cru-py/cru/value.py index 7b716f0..189f44f 100644 --- a/tools/cru-py/cru/value.py +++ b/tools/cru-py/cru/value.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import random import secrets import string @@ -6,7 +8,7 @@ from abc import abstractmethod, ABCMeta from collections.abc import Mapping, Callable from typing import Any, ClassVar, Literal, TypeVar, Generic, ParamSpec -from .excp import CruInternalLogicError, CruException +from .error import CruInternalError, CruException def _str_case_in(s: str, case: bool, l: list[str]) -> bool: @@ -16,86 +18,77 @@ def _str_case_in(s: str, case: bool, l: list[str]) -> bool: return s.lower() in [s.lower() for s in l] -_ValueTypeForward = type["ValueType"] - T = TypeVar("T") -class _ValueErrorMixin: - VALUE_TYPE_KEY = "value_type" - - CruException.ATTR_REGISTRY.make_builder( - VALUE_TYPE_KEY, - "The type of the value that causes the exception." - ).build() - +class CruValueError(CruException): + def __init__( + self, + message: str, + value: Any, + value_type: ValueType | None, + *args, + **kwargs, + ): + super().__init__( + message, + *args, + **kwargs, + ) + self._value = value + self._value_type = value_type -class ValidationError(CruException, _ValueErrorMixin): - def __init__(self, message: str, value: Any, value_type: _ValueTypeForward[T] | None, *args, **kwargs): - super().__init__(message, *args, value=value, type_=value_type.type, init_attrs={ - ValidationError.VALUE_TYPE_KEY: value_type, - }, **kwargs) + @property + def value(self) -> Any: + return self._value @property - def value_type(self) -> _ValueTypeForward[T] | None: - return self.get_attr(ValidationError.VALUE_TYPE_KEY) + def value_type(self) -> ValueType | None: + return self._value_type -class ValueStringConvertionError(CruException, _ValueErrorMixin): - def __init__(self, message: str, value: Any, value_type: _ValueTypeForward[T] | None, *args, - **kwargs): - super().__init__(message, *args, value=value, type_=value_type.type, init_attrs={ - ValueStringConvertionError.VALUE_TYPE_KEY: value_type, - }, **kwargs) +class CruValueValidationError(CruValueError): + pass - @property - def value_type(self) -> _ValueTypeForward[T] | None: - return self.get_attr(ValidationError.VALUE_TYPE_KEY) +class CruValueStringConversionError(CruValueError): + pass -class ValueType(Generic[T], metaclass=ABCMeta): - @staticmethod - def case_sensitive_to_str(case_sensitive: bool) -> str: - return f"case-{'' if case_sensitive else 'in'}sensitive" - def __init__(self, name: str) -> None: +# TODO: Continue here tomorrow! +class ValueType(Generic[T], metaclass=ABCMeta): + def __init__(self, name: str, _type: type[T]) -> None: self._name = name - self._type = type("T") + self._type = _type @property def name(self) -> str: return self._name @property - def type(self) -> type: + def type(self) -> type[T]: return self._type - def is_instance_of_value_type(self, value: Any) -> bool: + def check_value_type(self, value: Any) -> bool: return isinstance(value, self.type) - def _do_check_value(self, value: Any) -> tuple[True, T] | tuple[False, None | str]: - return True, value + def _do_check_value(self, value: Any) -> T: + return value def check_value(self, value: Any) -> T: if not isinstance(value, self.type): - raise ValidationError("Value type is wrong.", value, self) - ok, v_or_err = self._do_check_value(value) - if ok: - return v_or_err - else: - raise ValidationError(v_or_err or "Value is not valid.", value, self) + raise CruValueValidationError("Value type is wrong.", value, self) + return self._do_check_value(value) - @abstractmethod def _do_check_str_format(self, s: str) -> bool | tuple[bool, str]: - """ - Return None for no error. Otherwise, return error message. - """ 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 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) @@ -123,8 +116,9 @@ class ValueType(Generic[T], metaclass=ABCMeta): 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 ValidationError( + "Value is not valid and is not a str.", value_or_str, self, inner=e + ) class TextValueType(ValueType[str]): @@ -182,12 +176,21 @@ class BooleanValueType(ValueType[bool]): DEFAULT_TRUE_LIST: ClassVar[list[str]] = ["true", "yes", "y", "on", "1"] DEFAULT_FALSE_LIST: ClassVar[list[str]] = ["false", "no", "n", "off", "0"] - def __init__(self, *, case_sensitive=False, true_list: None | list[str] = None, - false_list: None | list[str] = None) -> None: + def __init__( + self, + *, + case_sensitive=False, + true_list: None | list[str] = None, + false_list: None | list[str] = None, + ) -> None: super().__init__("boolean") self._case_sensitive = case_sensitive - self._valid_true_strs: list[str] = true_list or BooleanValueType.DEFAULT_TRUE_LIST - self._valid_false_strs: list[str] = false_list or BooleanValueType.DEFAULT_FALSE_LIST + self._valid_true_strs: list[str] = ( + true_list or BooleanValueType.DEFAULT_TRUE_LIST + ) + self._valid_false_strs: list[str] = ( + false_list or BooleanValueType.DEFAULT_FALSE_LIST + ) @property def case_sensitive(self) -> bool: @@ -206,8 +209,12 @@ 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 _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.", + ) def _do_convert_value_to_str(self, value): return "True" if value else "False" @@ -218,8 +225,8 @@ 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} ]' + s = " | ".join([f'"{v}"' for v in valid_values]) + self._valid_value_str = f"[ {s} ]" super().__init__(f"enum{self._valid_value_str}") self._case_sensitive = case_sensitive self._valid_values = valid_values @@ -237,8 +244,12 @@ class EnumValueType(ValueType[str]): return ok, (value if ok else err) 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 _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}", + ) def _do_convert_value_to_str(self, value): return value @@ -251,13 +262,15 @@ TEXT_VALUE_TYPE = TextValueType() INTEGER_VALUE_TYPE = IntegerValueType() BOOLEAN_VALUE_TYPE = BooleanValueType() -P = ParamSpec('P') +P = ParamSpec("P") class ValueGenerator(Generic[T, P]): INTERACTIVE_KEY: ClassVar[Literal["interactive"]] = "interactive" - def __init__(self, f: Callable[P, T], /, attributes: None | Mapping[str, Any] = None) -> None: + def __init__( + self, f: Callable[P, T], /, attributes: None | Mapping[str, Any] = None + ) -> None: self._f = f self._attributes = attributes or {} @@ -280,9 +293,15 @@ class ValueGenerator(Generic[T, P]): 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 {}))) + 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, []]): @@ -297,13 +316,15 @@ class RandomStringValueGenerator(ValueGenerator[str, []]): def generate_random_string(): characters = string.ascii_letters + string.digits - random_string = ''.join(random_choice(characters) for _ in range(length)) + random_string = "".join(random_choice(characters) for _ in range(length)) return random_string return generate_random_string def __init__(self, length: int, secure: bool) -> None: - super().__init__(RandomStringValueGenerator._create_generate_ramdom_func(length, secure)) + super().__init__( + RandomStringValueGenerator._create_generate_ramdom_func(length, secure) + ) UUID_VALUE_GENERATOR = UuidValueGenerator() |