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.py157
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()