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.py287
1 files changed, 142 insertions, 145 deletions
diff --git a/tools/cru-py/cru/attr.py b/tools/cru-py/cru/attr.py
index 5d4f276..32cca8d 100644
--- a/tools/cru-py/cru/attr.py
+++ b/tools/cru-py/cru/attr.py
@@ -1,48 +1,40 @@
import copy
-from collections.abc import Callable
+from collections.abc import Callable, Iterable
from dataclasses import dataclass, field
-from typing import Any
+from typing import Any, ClassVar
-from .util import CanBeList, TypeSet, L, ListOperations
+from .util import CanBeList, TypeSet, F, L, WF, CruUniqueKeyInplaceList, CRU_NOT_FOUND, CRU_USE_DEFAULT, \
+ CRU_DONT_CHANGE, CRU_PLACEHOLDER
@dataclass
class CruAttr:
+ USE_DEFAULT: ClassVar = CRU_USE_DEFAULT
+
name: str
value: Any
description: str
-
-class _CruAttrVoid:
- _i = False
-
- def __init__(self):
- if self._i:
- raise ValueError("CruAttrNotSet is a singleton!")
- self._i = True
-
- def __copy__(self):
- return self
-
-
-CRU_ATTR_NOT_SET = _CruAttrVoid()
-CRU_ATTR_USE_DEFAULT = CRU_ATTR_NOT_SET.__copy__()
+ @staticmethod
+ def make(name: str, value: Any = USE_DEFAULT, description: str | None = None) -> "CruAttr":
+ return CruAttr(name, value, description)
-def _is_attr_void(v: Any) -> bool:
- return isinstance(v, _CruAttrVoid)
+CruAttrDefaultFactory = Callable[["CruAttrDef"], Any]
+CruAttrTransformer = Callable[[Any, "CruAttrDef"], Any]
+CruAttrValidator = Callable[[Any, "CruAttrDef"], None]
@dataclass
class CruAttrDef:
name: str
description: str
- default_factory: Callable[[], Any]
- transformer: Callable[[Any], Any]
- validator: Callable[[Any], None]
+ default_factory: CruAttrDefaultFactory
+ transformer: CruAttrTransformer
+ validator: CruAttrValidator
- def __init__(self, name: str, description: str, default_factory: Callable[[], Any],
- transformer: Callable[[Any], Any], validator: Callable[[Any], None]) -> 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
@@ -51,22 +43,22 @@ class CruAttrDef:
def transform(self, value: Any) -> Any:
if self.transformer is not None:
- return self.transformer(value)
+ return self.transformer(value, self)
return value
def validate(self, value: Any, /, force_allow_none: bool = False) -> None:
if force_allow_none is value is None:
return
if self.validator is not None:
- self.validator(value)
+ self.validator(value, self)
- def transform_and_validate(self, value: Any = CRU_ATTR_USE_DEFAULT, /, 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
def make_default_value(self) -> Any:
- return self.transform_and_validate(self.default_factory())
+ return self.transform_and_validate(self.default_factory(self))
def adopt(self, attr: CruAttr) -> CruAttr:
attr = copy.deepcopy(attr)
@@ -76,7 +68,7 @@ class CruAttrDef:
elif attr.name != self.name:
raise ValueError(f"Attr name is not match: {attr.name} != {self.name}")
- if attr.value is CRU_ATTR_NOT_SET:
+ if attr.value is CruAttr.USE_DEFAULT:
attr.value = self.make_default_value()
else:
attr.value = self.transform_and_validate(attr.value)
@@ -86,9 +78,8 @@ class CruAttrDef:
return attr
- def make(self, value: Any = CRU_ATTR_USE_DEFAULT, description: None | str = None) -> CruAttr:
- value = self.default_factory() if _is_attr_void(value) else value
- value = self.default_factory() if value is CRU_ATTR_USE_DEFAULT else value
+ 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
value = self.transform_and_validate(value)
return CruAttr(self.name, value,
description if description is not None else self.description)
@@ -96,17 +87,19 @@ class CruAttrDef:
@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=CRU_ATTR_NOT_SET)
- default_factory: Callable[[], Any] | None = field(default=None)
+ default: Any = field(default=CruAttr.USE_DEFAULT)
+ default_factory: CruAttrDefaultFactory | None = field(default=None)
auto_list: bool = field(default=False)
- transformers: list[Callable[[Any], Any]] = field(default_factory=list)
- validators: list[Callable[[Any], None]] = field(default_factory=list)
- override_transformer: Callable[[Any], Any] | None = field(default=None)
- override_validator: Callable[[Any], None] | None = field(default=None)
+ transformers: list[CruAttrTransformer] = field(default_factory=list)
+ validators: list[CruAttrValidator] = field(default_factory=list)
+ override_transformer: CruAttrTransformer | None = field(default=None)
+ override_validator: CruAttrValidator | None = field(default=None)
build_hook: Callable[[CruAttrDef], None] | None = field(default=None)
@@ -116,12 +109,12 @@ class CruAttrDefBuilder:
self.description = description
def auto_adjust_default(self) -> "CruAttrDefBuilder":
- if self.default is not CRU_ATTR_NOT_SET and self.default is not None:
+ if self.default is not CruAttr.USE_DEFAULT and self.default is not None:
return self
- if self.allow_none and _is_attr_void(self.default):
+ if self.allow_none and self.default is CruAttr.USE_DEFAULT:
self.default = None
if not self.allow_none and self.default is None:
- self.default = CRU_ATTR_NOT_SET
+ self.default = CruAttr.USE_DEFAULT
if self.auto_list and not self.allow_none:
self.default = []
@@ -133,49 +126,63 @@ class CruAttrDefBuilder:
self.description = default_description
return self
- def with_default(self, default: Any, is_factory: bool = False,
- reserve_if_not_set: bool = False) -> "CruAttrDefBuilder":
- if is_factory:
- self.default_factory = default
- else:
- if _is_attr_void(default) and reserve_if_not_set:
- return self
- else:
- self.default = default
+ 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:
+ 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
return self
- def with_default_factory(self, default_factory: Callable[[], Any]) -> "CruAttrDefBuilder":
- return self.with_default(default_factory, is_factory=True)
+ def with_default_factory(self, default_factory: CruAttrDefaultFactory) -> "CruAttrDefBuilder":
+ self.default_factory = default_factory
+ return self
- def with_types(self, allowed_types: CanBeList[type], default: Any = CRU_ATTR_NOT_SET) -> "CruAttrDefBuilder":
+ def with_types(self, allowed_types: CanBeList[type], default: Any = DONT_CHANGE) -> "CruAttrDefBuilder":
self.types = allowed_types
- if _is_attr_void(default):
+ if default is not CruAttrDefBuilder.DONT_CHANGE:
self.default = default
return self
- def with_allow_none(self, allow_none: bool, default: Any = CRU_ATTR_NOT_SET) -> "CruAttrDefBuilder":
+ def with_allow_none(self, allow_none: bool, default: Any = DONT_CHANGE) -> "CruAttrDefBuilder":
self.allow_none = allow_none
- if _is_attr_void(default):
+ if default is not CruAttrDefBuilder.DONT_CHANGE:
self.default = default
return self
- def with_optional(self, default: Any = CRU_ATTR_NOT_SET) -> "CruAttrDefBuilder":
+ def with_optional(self, default: Any = DONT_CHANGE) -> "CruAttrDefBuilder":
return self.with_allow_none(True, default)
- def with_required(self, default: Any = CRU_ATTR_NOT_SET) -> "CruAttrDefBuilder":
+ def with_required(self, default: Any = DONT_CHANGE) -> "CruAttrDefBuilder":
return self.with_allow_none(False, default)
- def with_constraint(self, required: bool, allowed_types: CanBeList[type] = None, /,
- default: Any = CRU_ATTR_NOT_SET, auto_list=False, *, default_is_factory: bool = False,
- default_reserve_if_not_set: bool = True) -> "CruAttrDefBuilder":
- return (self.with_allow_none(required).with_types(allowed_types).with_auto_list(auto_list).
- with_default(default, default_is_factory, default_reserve_if_not_set))
+ 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):
+ 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], Any] | None) -> "CruAttrDefBuilder":
+ def add_transformer(self, transformer: Callable[[Any, "CruAttrDef"], Any] | None) -> "CruAttrDefBuilder":
if transformer is not None:
self.transformers.append(transformer)
return self
@@ -184,7 +191,7 @@ class CruAttrDefBuilder:
self.transformers.clear()
return self
- def add_validator(self, validator: Callable[[Any], None] | None) -> "CruAttrDefBuilder":
+ def add_validator(self, validator: Callable[[Any, "CruAttrDef"], None] | None) -> "CruAttrDefBuilder":
if validator is not None:
self.validators.append(validator)
return self
@@ -217,32 +224,38 @@ class CruAttrDefBuilder:
valid, err = b.is_valid()
if not valid: raise ValueError(err)
- def composed_transformer(v: Any):
+ 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)
- for t in b.transformers:
- v = t(v)
+ v = v.transform(transformer)
+ else:
+ v = transformer(v)
return v
type_set = TypeSet(b.types)
- def composed_validator(v: Any):
+ 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)
- ListOperations.foreach(v, *b.validators)
+ L(v).foreach(validator)
else:
type_set.check_value(v, allow_none=b.allow_none)
- for vl in b.validators: vl(v)
+ validator(v)
- validator = b.override_validator or composed_validator
- transformer = b.override_transformer or composed_transformer
+ real_transformer = b.override_transformer or composed_transformer
+ real_validator = b.override_validator or composed_validator
default_factory = b.default_factory
- if b.default_factory is None:
- default_factory = lambda: copy.deepcopy(b.default)
- validator(default_factory())
+ if default_factory is None:
+ default_factory = lambda _d: copy.deepcopy(b.default)
- d = CruAttrDef(b.name, b.description, default_factory, transformer, validator)
+ d = CruAttrDef(b.name, b.description, default_factory, real_transformer, real_validator)
if b.build_hook: b.build_hook(d)
return d
@@ -252,93 +265,77 @@ class CruAttrDefBuilder:
return CruAttrDefBuilder._build(c, auto_adjust_default)
-class CruAttrDefRegistry:
+class CruAttrDefRegistry(CruUniqueKeyInplaceList[CruAttrDef, str]):
def __init__(self) -> None:
- self._def_list = []
-
- @property
- def items(self) -> list[CruAttrDef]:
- return self._def_list
-
- def register(self, def_: CruAttrDef):
- for i in self._def_list:
- if i.name == def_.name:
- raise ValueError(f"Attribute {def_.name} already exists!")
- self._def_list.append(def_)
+ super().__init__(lambda d: d.name)
def make_builder(self, name: str, default_description: str) -> CruAttrDefBuilder:
b = CruAttrDefBuilder(name, default_description)
- b.build_hook = lambda a: self.register(a)
+ b.build_hook = lambda a: self.add(a)
return b
- def get_item_optional(self, name: str) -> CruAttrDef | None:
- for i in self._def_list:
- if i.name == name:
- return i
- return None
+ def adopt(self, attr: CruAttr) -> CruAttr:
+ d = self.get(attr.name)
+ return d.adopt(attr)
-class CruAttrTable:
+class CruAttrTable(CruUniqueKeyInplaceList[CruAttr, str]):
def __init__(self, registry: CruAttrDefRegistry) -> None:
self._registry: CruAttrDefRegistry = registry
- self._attrs: list[CruAttr] = []
-
- def get_attr_or(self, name: str, fallback: Any = None) -> CruAttr | Any:
- for a in self._attrs:
- if a.name == name:
- return a
- return fallback
-
- def get_attr(self, name) -> CruAttr:
- a = self.get_attr_or(name, None)
- if a is None:
- raise KeyError(f"Attribute {name} not found!")
- return a
-
- def get_value_or(self, name: str, fallback: Any = None) -> Any:
- a = self.get_attr_or(name, None)
- if a is None:
+ super().__init__(lambda a: a.name, before_add=registry.adopt)
+
+ @property
+ 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:
return fallback
return a.value
def get_value(self, name: str) -> Any:
- a = self.get_attr(name)
+ a = self.get(name)
return a.value
- def get_def_or(self, name: str, fallback: Any = None) -> CruAttrDef | Any:
- d = self._registry.get_item_optional(name)
- return d if d is not None else fallback
+ def make_attr(self, name: str, value: Any = CruAttr.USE_DEFAULT, /, description: str | None = None) -> CruAttr:
+ d = self._registry.get(name)
+ return d.make(value, description or d.description)
- def get_def(self, name: str) -> CruAttrDef:
- d = self._registry.get_item_optional(name)
- if d is None:
- raise KeyError(f"Attribute definition {name} not found!")
- return d
+ def add_value(self, name: str, value: Any = CruAttr.USE_DEFAULT, /, description: str | None = None, *,
+ replace: bool = False) -> CruAttr:
+ attr = self.make_attr(name, value, description)
+ self.add(attr, replace)
+ return attr
- def _check_already_exist(self, name: str) -> None:
- if name in self:
- raise KeyError(f"Attribute {name} already exists!")
+ 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 add_attr(self, attr: CruAttr) -> CruAttr:
- self._check_already_exist(attr.name)
- d = self.get_def(attr.name)
- attr = d.adopt(attr)
- self._attrs.append(attr)
- return attr
+ 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
- def add(self, name: str, value: Any = CRU_ATTR_USE_DEFAULT, /, description: str | None = None) -> CruAttr:
- self._check_already_exist(name)
- d = self.get_def(name)
- attr = d.make(value, description or d.description)
- self._attrs.append(attr)
- return attr
+ 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
- def remove(self, name: str, /, allow_absense: bool = True) -> None:
- l = [a for a in self._attrs if a.name == name]
- if len(l) != len(self._attrs) and not allow_absense:
- raise KeyError(f"Attribute {name} not found!")
- self._attrs = l
+ 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
- def __contains__(self, name):
- return any(a.name == name for a in self._attrs)
+ raise TypeError(f"Unsupported type: {type(a)}")