aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/attr.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/cru-py/cru/attr.py')
-rw-r--r--tools/cru-py/cru/attr.py335
1 files changed, 179 insertions, 156 deletions
diff --git a/tools/cru-py/cru/attr.py b/tools/cru-py/cru/attr.py
index a52585a..82f1eba 100644
--- a/tools/cru-py/cru/attr.py
+++ b/tools/cru-py/cru/attr.py
@@ -1,22 +1,27 @@
+from __future__ import annotations
+
import copy
from collections.abc import Callable, Iterable
from dataclasses import dataclass, field
-from typing import Any, ClassVar
+from typing import Any
-from ._util import CanBeList, TypeSet, F, L, WF, CruUniqueKeyInplaceList, CRU_NOT_FOUND, CRU_USE_DEFAULT, \
- CRU_DONT_CHANGE, CRU_PLACEHOLDER
+from ._list import CruUniqueKeyList
+from ._type import CruTypeSet
+from ._const import CruNotFound, CruUseDefault, CruDontChange
+from ._iter import CruIterator
@dataclass
class CruAttr:
- USE_DEFAULT: ClassVar = CRU_USE_DEFAULT
name: str
value: Any
- description: str
+ description: str | None
@staticmethod
- def make(name: str, value: Any = USE_DEFAULT, description: str | None = None) -> "CruAttr":
+ def make(
+ name: str, value: Any = CruUseDefault.VALUE, description: str | None = None
+ ) -> CruAttr:
return CruAttr(name, value, description)
@@ -33,8 +38,14 @@ class CruAttrDef:
transformer: CruAttrTransformer
validator: CruAttrValidator
- def __init__(self, name: str, description: str, default_factory: CruAttrDefaultFactory,
- transformer: CruAttrTransformer, validator: CruAttrValidator) -> None:
+ def __init__(
+ self,
+ name: str,
+ description: str,
+ default_factory: CruAttrDefaultFactory,
+ transformer: CruAttrTransformer,
+ validator: CruAttrValidator,
+ ) -> None:
self.name = name
self.description = description
self.default_factory = default_factory
@@ -52,7 +63,9 @@ class CruAttrDef:
if self.validator is not None:
self.validator(value, self)
- def transform_and_validate(self, value: Any, /, force_allow_none: bool = False) -> Any:
+ def transform_and_validate(
+ self, value: Any, /, force_allow_none: bool = False
+ ) -> Any:
value = self.transform(value)
self.validate(value, force_allow_none)
return value
@@ -68,7 +81,7 @@ class CruAttrDef:
elif attr.name != self.name:
raise ValueError(f"Attr name is not match: {attr.name} != {self.name}")
- if attr.value is CruAttr.USE_DEFAULT:
+ if attr.value is CruUseDefault.VALUE:
attr.value = self.make_default_value()
else:
attr.value = self.transform_and_validate(attr.value)
@@ -78,22 +91,26 @@ class CruAttrDef:
return attr
- def make(self, value: Any = CruAttr.USE_DEFAULT, description: None | str = None) -> CruAttr:
- value = self.make_default_value() if value is CruAttr.USE_DEFAULT else value
+ def make(
+ self, value: Any = CruUseDefault.VALUE, description: None | str = None
+ ) -> CruAttr:
+ value = self.make_default_value() if value is CruUseDefault.VALUE else value
value = self.transform_and_validate(value)
- return CruAttr(self.name, value,
- description if description is not None else self.description)
+ return CruAttr(
+ self.name,
+ value,
+ description if description is not None else self.description,
+ )
@dataclass
class CruAttrDefBuilder:
- DONT_CHANGE: ClassVar = CRU_DONT_CHANGE
name: str
description: str
- types: CanBeList[type] = field(default=None)
- allow_none: bool = field(default=True)
- default: Any = field(default=CruAttr.USE_DEFAULT)
+ types: list[type] | None = field(default=None)
+ allow_none: bool = field(default=False)
+ default: Any = field(default=CruUseDefault.VALUE)
default_factory: CruAttrDefaultFactory | None = field(default=None)
auto_list: bool = field(default=False)
transformers: list[CruAttrTransformer] = field(default_factory=list)
@@ -108,104 +125,106 @@ class CruAttrDefBuilder:
self.name = name
self.description = description
- def auto_adjust_default(self) -> "CruAttrDefBuilder":
- if self.default is not CruAttr.USE_DEFAULT and self.default is not None:
- return self
- if self.allow_none and self.default is CruAttr.USE_DEFAULT:
+ def auto_adjust_default(self) -> None:
+ if self.default is not CruUseDefault.VALUE and self.default is not None:
+ return
+ if self.allow_none and self.default is CruUseDefault.VALUE:
self.default = None
if not self.allow_none and self.default is None:
- self.default = CruAttr.USE_DEFAULT
+ self.default = CruUseDefault.VALUE
if self.auto_list and not self.allow_none:
self.default = []
- def with_name(self, name: str) -> "CruAttrDefBuilder":
- self.name = name
+ def with_name(self, name: str | CruDontChange) -> CruAttrDefBuilder:
+ if name is not CruDontChange.VALUE:
+ self.name = name
return self
- def with_description(self, default_description: str) -> "CruAttrDefBuilder":
- self.description = default_description
+ def with_description(
+ self, default_description: str | CruDontChange
+ ) -> CruAttrDefBuilder:
+ if default_description is not CruDontChange.VALUE:
+ self.description = default_description
return self
- def with_default(self, /, default: Any | DONT_CHANGE = DONT_CHANGE,
- default_factory: CruAttrDefaultFactory | DONT_CHANGE = DONT_CHANGE) -> "CruAttrDefBuilder":
- if default is not CruAttrDefBuilder.DONT_CHANGE:
+ def with_default(self, default: Any) -> CruAttrDefBuilder:
+ if default is not CruDontChange.VALUE:
self.default = default
- if default_factory is not CruAttrDefBuilder.DONT_CHANGE:
- self.default_factory = default_factory
return self
- def with_default_value(self, default: Any) -> "CruAttrDefBuilder":
- self.default = default
+ def with_default_factory(
+ self,
+ default_factory: CruAttrDefaultFactory | CruDontChange,
+ ) -> CruAttrDefBuilder:
+ if default_factory is not CruDontChange.VALUE:
+ self.default_factory = default_factory
return self
- def with_default_factory(self, default_factory: CruAttrDefaultFactory) -> "CruAttrDefBuilder":
- self.default_factory = default_factory
+ def with_types(
+ self,
+ types: Iterable[type] | None | CruDontChange,
+ ) -> CruAttrDefBuilder:
+ if types is not CruDontChange.VALUE:
+ self.types = None if types is None else list(types)
return self
- def with_types(self, allowed_types: CanBeList[type], default: Any = DONT_CHANGE) -> "CruAttrDefBuilder":
- self.types = allowed_types
- if default is not CruAttrDefBuilder.DONT_CHANGE:
- self.default = default
+ def with_allow_none(self, allow_none: bool | CruDontChange) -> CruAttrDefBuilder:
+ if allow_none is not CruDontChange.VALUE:
+ self.allow_none = allow_none
return self
- def with_allow_none(self, allow_none: bool, default: Any = DONT_CHANGE) -> "CruAttrDefBuilder":
- self.allow_none = allow_none
- if default is not CruAttrDefBuilder.DONT_CHANGE:
- self.default = default
- return self
-
- def with_optional(self, default: Any = DONT_CHANGE) -> "CruAttrDefBuilder":
- return self.with_allow_none(True, default)
-
- def with_required(self, default: Any = DONT_CHANGE) -> "CruAttrDefBuilder":
- return self.with_allow_none(False, default)
-
- def with_constraint(self, /, required: bool = DONT_CHANGE, allowed_types: CanBeList[type] = DONT_CHANGE,
- default: Any = DONT_CHANGE, default_factory: CruAttrDefaultFactory = DONT_CHANGE,
- auto_list: bool = DONT_CHANGE) -> "CruAttrDefBuilder":
- def should_change(v):
- return v is not CruAttrDefBuilder.DONT_CHANGE
-
- if should_change(required):
- self.allow_none = not required
- if should_change(allowed_types):
- self.types = allowed_types
- if should_change(default):
- self.default = default
- if should_change(default_factory):
- self.default_factory = default_factory
- if should_change(auto_list):
+ def with_auto_list(
+ self, auto_list: bool | CruDontChange = True
+ ) -> CruAttrDefBuilder:
+ if auto_list is not CruDontChange.VALUE:
self.auto_list = auto_list
return self
- def with_auto_list(self, transform_auto_list: bool = True) -> "CruAttrDefBuilder":
- self.auto_list = transform_auto_list
- return self
-
- def add_transformer(self, transformer: Callable[[Any, "CruAttrDef"], Any] | None) -> "CruAttrDefBuilder":
- if transformer is not None:
- self.transformers.append(transformer)
+ def with_constraint(
+ self,
+ /,
+ allow_none: bool | CruDontChange = CruDontChange.VALUE,
+ types: Iterable[type] | None | CruDontChange = CruDontChange.VALUE,
+ default: Any = CruDontChange.VALUE,
+ default_factory: CruAttrDefaultFactory | CruDontChange = CruDontChange.VALUE,
+ auto_list: bool | CruDontChange = CruDontChange.VALUE,
+ ) -> CruAttrDefBuilder:
+ return (
+ self.with_allow_none(allow_none)
+ .with_types(types)
+ .with_default(default)
+ .with_default_factory(default_factory)
+ .with_auto_list(auto_list)
+ )
+
+ def add_transformer(self, transformer: CruAttrTransformer) -> CruAttrDefBuilder:
+ self.transformers.append(transformer)
return self
- def clear_transformers(self) -> "CruAttrDefBuilder":
+ def clear_transformers(self) -> CruAttrDefBuilder:
self.transformers.clear()
return self
- def add_validator(self, validator: Callable[[Any, "CruAttrDef"], None] | None) -> "CruAttrDefBuilder":
- if validator is not None:
- self.validators.append(validator)
+ def add_validator(self, validator: CruAttrValidator) -> CruAttrDefBuilder:
+ self.validators.append(validator)
return self
- def clear_validators(self) -> "CruAttrDefBuilder":
+ def clear_validators(self) -> CruAttrDefBuilder:
self.validators.clear()
return self
- def with_override_transformer(self, override_transformer: Callable[[Any], Any] | None) -> "CruAttrDefBuilder":
- self.override_transformer = override_transformer
+ def with_override_transformer(
+ self, override_transformer: CruAttrTransformer | None | CruDontChange
+ ) -> CruAttrDefBuilder:
+ if override_transformer is not CruDontChange.VALUE:
+ self.override_transformer = override_transformer
return self
- def with_override_validator(self, override_validator: Callable[[Any], None] | None) -> "CruAttrDefBuilder":
- self.override_validator = override_validator
+ def with_override_validator(
+ self, override_validator: CruAttrValidator | None | CruDontChange
+ ) -> CruAttrDefBuilder:
+ if override_validator is not CruDontChange.VALUE:
+ self.override_validator = override_validator
return self
def is_valid(self) -> tuple[bool, str]:
@@ -213,50 +232,72 @@ class CruAttrDefBuilder:
return False, "Name must be a string!"
if not isinstance(self.description, str):
return False, "Default description must be a string!"
- if not self.allow_none and self.default is None and self.default_factory is None:
+ if (
+ not self.allow_none
+ and self.default is None
+ and self.default_factory is None
+ ):
return False, "Default must be set if allow_none is False!"
+ return True, ""
@staticmethod
- def _build(b: "CruAttrDefBuilder", auto_adjust_default: bool = True) -> CruAttrDef:
+ def _build(
+ builder: CruAttrDefBuilder, auto_adjust_default: bool = True
+ ) -> CruAttrDef:
if auto_adjust_default:
- b.auto_adjust_default()
-
- valid, err = b.is_valid()
- if not valid: raise ValueError(err)
-
- def composed_transformer(v: Any, d_):
- transformers = L(b.transformers)
- transformers.transform(lambda f: F(f).bind(CRU_PLACEHOLDER, d_))
- transformer = F.make_chain(*transformers) if not transformers.is_empty else WF.only_you
- if b.auto_list:
- v = L.make(v)
- v = v.transform(transformer)
+ builder.auto_adjust_default()
+
+ valid, err = builder.is_valid()
+ if not valid:
+ raise ValueError(err)
+
+ def composed_transformer(value: Any, attr_def: CruAttrDef) -> Any:
+ def transform_value(single_value: Any) -> Any:
+ for transformer in builder.transformers:
+ single_value = transformer(single_value, attr_def)
+ return single_value
+
+ if builder.auto_list:
+ if not isinstance(value, list):
+ value = [value]
+ value = CruIterator(value).transform(transform_value).to_list()
+
else:
- v = transformer(v)
- return v
-
- type_set = TypeSet(b.types)
-
- def composed_validator(v: Any, d_):
- validators = L(b.validators)
- validators.transform(lambda f: F(f).bind(CRU_PLACEHOLDER, d_))
- validator = F.make_chain(*validators) if not validators.is_empty else WF.true
- if b.auto_list:
- type_set.check_value_list(v, allow_none=b.allow_none)
- L(v).foreach(validator)
+ value = transform_value(value)
+ return value
+
+ type_set = None if builder.types is None else CruTypeSet(*builder.types)
+
+ def composed_validator(value: Any, attr_def: CruAttrDef):
+ def validate_value(single_value: Any) -> None:
+ if type_set is not None:
+ type_set.check_value(single_value, allow_none=builder.allow_none)
+ for validator in builder.validators:
+ validator(single_value, attr_def)
+
+ if builder.auto_list:
+ CruIterator(value).foreach(validate_value)
else:
- type_set.check_value(v, allow_none=b.allow_none)
- validator(v)
+ validate_value(value)
- real_transformer = b.override_transformer or composed_transformer
- real_validator = b.override_validator or composed_validator
+ real_transformer = builder.override_transformer or composed_transformer
+ real_validator = builder.override_validator or composed_validator
- default_factory = b.default_factory
+ default_factory = builder.default_factory
if default_factory is None:
- default_factory = lambda _d: copy.deepcopy(b.default)
- d = CruAttrDef(b.name, b.description, default_factory, real_transformer, real_validator)
- if b.build_hook: b.build_hook(d)
+ def default_factory(_d):
+ return copy.deepcopy(builder.default)
+
+ d = CruAttrDef(
+ builder.name,
+ builder.description,
+ default_factory,
+ real_transformer,
+ real_validator,
+ )
+ if builder.build_hook:
+ builder.build_hook(d)
return d
def build(self, auto_adjust_default=True) -> CruAttrDef:
@@ -265,7 +306,7 @@ class CruAttrDefBuilder:
return CruAttrDefBuilder._build(c, auto_adjust_default)
-class CruAttrDefRegistry(CruUniqueKeyInplaceList[CruAttrDef, str]):
+class CruAttrDefRegistry(CruUniqueKeyList[CruAttrDef, str]):
def __init__(self) -> None:
super().__init__(lambda d: d.name)
@@ -280,7 +321,7 @@ class CruAttrDefRegistry(CruUniqueKeyInplaceList[CruAttrDef, str]):
return d.adopt(attr)
-class CruAttrTable(CruUniqueKeyInplaceList[CruAttr, str]):
+class CruAttrTable(CruUniqueKeyList[CruAttr, str]):
def __init__(self, registry: CruAttrDefRegistry) -> None:
self._registry: CruAttrDefRegistry = registry
super().__init__(lambda a: a.name, before_add=registry.adopt)
@@ -289,9 +330,9 @@ class CruAttrTable(CruUniqueKeyInplaceList[CruAttr, str]):
def registry(self) -> CruAttrDefRegistry:
return self._registry
- def get_value_or(self, name: str, fallback: Any = CRU_NOT_FOUND) -> Any:
- a = self.get_or(name, CRU_NOT_FOUND)
- if a is CRU_NOT_FOUND:
+ def get_value_or(self, name: str, fallback: Any = CruNotFound.VALUE) -> Any:
+ a = self.get_or(name, CruNotFound.VALUE)
+ if a is CruNotFound.VALUE:
return fallback
return a.value
@@ -299,43 +340,25 @@ class CruAttrTable(CruUniqueKeyInplaceList[CruAttr, str]):
a = self.get(name)
return a.value
- def make_attr(self, name: str, value: Any = CruAttr.USE_DEFAULT, /, description: str | None = None) -> CruAttr:
+ def make_attr(
+ self,
+ name: str,
+ value: Any = CruUseDefault.VALUE,
+ /,
+ description: str | None = None,
+ ) -> CruAttr:
d = self._registry.get(name)
return d.make(value, description or d.description)
- def add_value(self, name: str, value: Any = CruAttr.USE_DEFAULT, /, description: str | None = None, *,
- replace: bool = False) -> CruAttr:
+ def add_value(
+ self,
+ name: str,
+ value: Any = CruUseDefault.VALUE,
+ /,
+ description: str | None = None,
+ *,
+ replace: bool = False,
+ ) -> CruAttr:
attr = self.make_attr(name, value, description)
self.add(attr, replace)
return attr
-
- def extend_values(self, *t: tuple[str, Any, str | None], replace: bool = False) -> None:
- t = [self.make_attr(n, v, d) for n, v, d in t]
- self.extend(t, replace)
-
- def extend_with(self, a: CruAttr | Iterable[CruAttr | tuple[str, Any, str | None]] | dict[
- str, Any | tuple[Any, str]] | None = None, replace: bool = False):
- if a is None: return
-
- if isinstance(a, CruAttr):
- self.add(a, replace)
- return
-
- if isinstance(a, dict):
- l = L()
- for k, v in a.items():
- if isinstance(v, tuple):
- v, d = v
- else:
- d = None
- l.append(self.make_attr(k, v, d))
- self.extend(l, replace)
- return
-
- if isinstance(a, Iterable):
- l = L(a)
- l.transform_if(lambda n_, v_, d_: self.make_attr(n_, v_, d_), F(isinstance).bind(CRU_PLACEHOLDER, tuple))
- self.extend(l, replace)
- return
-
- raise TypeError(f"Unsupported type: {type(a)}")