diff --git a/src/dabmodel/__init__.py b/src/dabmodel/__init__.py index 5cbe0b4..128542a 100644 --- a/src/dabmodel/__init__.py +++ b/src/dabmodel/__init__.py @@ -12,6 +12,7 @@ Main module __init__ file. from .__metadata__ import __version__, __Summuary__, __Name__ from .model import ( + DABFieldInfo, BaseAppliance, BaseFeature, DABModelException, diff --git a/src/dabmodel/model.py b/src/dabmodel/model.py index 1fb2322..3a37202 100644 --- a/src/dabmodel/model.py +++ b/src/dabmodel/model.py @@ -88,36 +88,50 @@ def _peel_annotated(t: Any) -> Any: return t -def __check_annotation_definition__(_type) -> bool: +def _check_annotation_definition(_type) -> bool: _type = _peel_annotated(_type) # handle Optional[] and Union[None,...] if (get_origin(_type) is Union or get_origin(_type) is UnionType) and type(None) in get_args(_type): - return all([__check_annotation_definition__(_) for _ in get_args(_type) if _ is not type(None)]) + return all([_check_annotation_definition(_) for _ in get_args(_type) if _ is not type(None)]) # handle other Union[...] if get_origin(_type) is Union or get_origin(_type) is UnionType: - return all([__check_annotation_definition__(_) for _ in get_args(_type)]) + return all([_check_annotation_definition(_) for _ in get_args(_type)]) # handle Dict[...] if get_origin(_type) is dict: inner = get_args(_type) if len(inner) != 2: raise IncompletelyAnnotatedField(f"Dict Annotation requires 2 inner definitions: {_type}") - return _peel_annotated(inner[0]) in ALLOWED_MODEL_FIELDS_TYPES and __check_annotation_definition__(inner[1]) + return _peel_annotated(inner[0]) in ALLOWED_MODEL_FIELDS_TYPES and _check_annotation_definition(inner[1]) + + # handle Tuple[] + if get_origin(_type) in [tuple]: + inner_types = get_args(_type) + if len(inner_types) == 0: + raise IncompletelyAnnotatedField(f"Annotation requires inner definition: {_type}") + if len(inner_types) == 2 and inner_types[1] is Ellipsis: + return _check_annotation_definition(inner_types[0]) + return all([_check_annotation_definition(_) for _ in inner_types]) # handle Set[],Tuple[],FrozenSet[],List[] if get_origin(_type) in [set, frozenset, tuple, list]: inner_types = get_args(_type) if len(inner_types) == 0: raise IncompletelyAnnotatedField(f"Annotation requires inner definition: {_type}") - return all([__check_annotation_definition__(_) for _ in inner_types]) + return all([_check_annotation_definition(_) for _ in inner_types]) if _type in ALLOWED_MODEL_FIELDS_TYPES: return True return False -class Constraint: ... +class BaseConstraint(Generic[TV_ALLOWED_MODEL_FIELDS_TYPES]): + _bound_type: type + + def __init__(self): ... + + def check(self, value: TV_ALLOWED_MODEL_FIELDS_TYPES) -> bool: ... def _deepfreeze(value): @@ -132,38 +146,85 @@ def _deepfreeze(value): return value +class DABFieldInfo: + def __init__(self, *, doc: str = "", constraints: list[BaseConstraint] = []): + self._doc: str = doc + self._constraints: list[BaseConstraint] = constraints + + @property + def doc(self): + return self._doc + + @property + def constraints(self): + return self._constraints + + class DABField(Generic[TV_ALLOWED_MODEL_FIELDS_TYPES]): - def __init__(self, name: str, v: Optional[TV_ALLOWED_MODEL_FIELDS_TYPES], a: Any): - self._constraints: List[Constraint] = [] + def __init__(self, name: str, v: Optional[TV_ALLOWED_MODEL_FIELDS_TYPES], a: Any, i: str): self._name: str = name self._source: Optional[type] = None self._default_value: Optional[TV_ALLOWED_MODEL_FIELDS_TYPES] = v self._value: Optional[TV_ALLOWED_MODEL_FIELDS_TYPES] = v self._annotations: Any = a - self._documentation: str = "" - - def add_documentation(self, d: str) -> None: - self._documentation = d + self._info: DABFieldInfo = i + self._constraints: List[BaseConstraint] = i.constraints def add_source(self, s: type) -> None: self._source = s - def add_constraint(self, c: Constraint) -> None: + @property + def doc(self): + return self._info.doc + + def add_constraint(self, c: BaseConstraint) -> None: self._constraints.append(c) + @property + def constraints(self) -> list[BaseConstraint]: + return self._info.constraints + + @property + def default_value(self): + return _deepfreeze(self._default_value) + def update_value(self, v: Optional[TV_ALLOWED_MODEL_FIELDS_TYPES] = None) -> None: self._value = v - def render(self) -> TV_ALLOWED_MODEL_FIELDS_TYPES: + @property + def value(self): return _deepfreeze(self._value) - def render_default(self) -> TV_ALLOWED_MODEL_FIELDS_TYPES: - return _deepfreeze(self._default_value) - - def get_annotation(self) -> Any: + @property + def annotations(self) -> Any: return self._annotations +class FrozenDABField(Generic[TV_ALLOWED_MODEL_FIELDS_TYPES]): + def __init__(self, inner_field: DABField): + self._inner_field = inner_field + + @property + def doc(self): + return self._inner_field.doc + + @property + def constraints(self): + return _deepfreeze(self._inner_field.constraints) + + @property + def default_value(self): + return self._inner_field.default_value + + @property + def value(self): + return self._inner_field.value + + @property + def annotations(self) -> Any: + return _deepfreeze(self._inner_field.annotations) + + class BaseMeta(type): def __new__(mcls, name, bases, namespace): # print("__NEW__ Defining:", name, "with keys:", list(namespace)) @@ -198,12 +259,12 @@ class BaseMeta(type): # Modified fields if len(bases) == 1 and _fname in namespace["__DABSchema__"].keys(): # print(f"Modified field: {_fname}") - if _fname in namespace["__annotations__"]: + if "__annotations__" in namespace and _fname in namespace["__annotations__"]: raise ReadOnlyFieldAnnotation("annotations cannot be modified on derived classes") try: check_type( _fvalue, - namespace["__DABSchema__"][_fname].get_annotation(), + namespace["__DABSchema__"][_fname].annotations, collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ) except TypeCheckError as exp: @@ -225,9 +286,22 @@ class BaseMeta(type): if isinstance(namespace["__annotations__"][_fname], str): namespace["__annotations__"][_fname] = _resolve_annotation(namespace["__annotations__"][_fname]) - if not __check_annotation_definition__(namespace["__annotations__"][_fname]): + if not _check_annotation_definition(namespace["__annotations__"][_fname]): raise InvalidFieldAnnotation(f"Field <{_fname}> has not an allowed or valid annotation.") + _finfo: Optional[DABFieldInfo] = DABFieldInfo() + origin = get_origin(namespace["__annotations__"][_fname]) + name = getattr(origin, "__name__", "") or getattr(origin, "__qualname__", "") or str(origin) + if "Annotated" in name: + args = get_args(namespace["__annotations__"][_fname]) + if args: + if len(args) > 2: + raise InvalidFieldAnnotation(f"Field <{_fname}> had invalid Annotated value.") + if len(args) == 2 and not isinstance(args[1], DABFieldInfo): + raise InvalidFieldAnnotation(f"Only DABFieldInfo object is allowed as Annotated data.") + + _finfo = args[1] + # print(f"annotation is: {namespace['__annotations__'][_fname]}") # check if value is valid try: @@ -238,8 +312,7 @@ class BaseMeta(type): raise InvalidFieldValue( f"Value of Field <{_fname}> is not of expected type {namespace['__annotations__'][_fname]}." ) from exp - new_fields[_fname] = DABField(_fname, _fvalue, namespace["__annotations__"][_fname]) - # namespace[_fname].add_documentation() + new_fields[_fname] = DABField(_fname, _fvalue, namespace["__annotations__"][_fname], _finfo) # removing modified fields from class (will add them back later) for _fname in new_fields.keys(): @@ -279,9 +352,13 @@ class BaseMeta(type): obj = super().__call__(*args, **kw) for _fname in cls.__DABSchema__.keys(): - setattr(obj, _fname, cls.__DABSchema__[_fname].render()) + setattr(obj, _fname, cls.__DABSchema__[_fname].value) # obj.__DABSchema__ = deepfreeze(obj.__DABSchema__) # setattr(obj, "__DABSchema__", deepfreeze(obj.__DABSchema__)) + inst_schema = dict() + for _fname, _fvalue in cls.__DABSchema__.items(): + inst_schema[_fname] = FrozenDABField(_fvalue) + setattr(obj, "__DABSchema__", inst_schema) return obj diff --git a/test/test_model.py b/test/test_model.py index 6963942..5997bfc 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -175,13 +175,6 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase): class _(dm.BaseAppliance): _ = "default value" - def test_annotated(self): - """Testing first appliance level, and Field types (annotated one)""" - - # class can be created if annotation is a string - class Appliance1(dm.BaseAppliance): - StrVar: Annotated[str, "my string"] = "default value" - def test_optionnal(self): """Testing first appliance level, and Field types (Optionnal annotations)""" @@ -492,6 +485,7 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase): testVar4: "Tuple[str,...]" = ("a", "c") testVar5: tuple[str, ...] = ("a", "b") testVar6: "tuple[str,...]" = ("a", "b") + testVar7: Tuple[int, str] = (1, "b") # testVar7: Tuple[Union[int, str]] = (1, 2, 3, "one", "two", "three") app1 = Appliance1() @@ -502,7 +496,7 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase): self.immutable_vars__test_field(app1, "testVar4", ("a", "c"), ("h", "e")) self.immutable_vars__test_field(app1, "testVar5", ("a", "b"), ("h", "c")) self.immutable_vars__test_field(app1, "testVar6", ("a", "b"), ("h", "c")) - # self.immutable_vars__test_field(app1, "testVar7", (1, 2, 3, "one", "two", "three"), ("h", "c")) + self.immutable_vars__test_field(app1, "testVar7", (1, "b"), (7, "h")) # must work sorted(app1.testVar) @@ -527,6 +521,381 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase): class _(dm.BaseAppliance): _: "Tuple" = (1, 2) + with self.assertRaises(dm.InvalidFieldValue): + + class _(dm.BaseAppliance): + _: "Tuple[int,...]" = (1, "a") + + with self.assertRaises(dm.InvalidFieldValue): + + class _(dm.BaseAppliance): + _: "tuple[int,...]" = (1, "a") + + def check_immutable_fields_schema( + self, appliance: dm.BaseAppliance, field_name: str, expected_value: str, expected_default_value: str, expected_type: type + ): + self.assertIn(field_name, appliance.__DABSchema__) + self.assertIn("doc", dir(appliance.__DABSchema__[field_name])) + self.assertEqual(appliance.__DABSchema__[field_name].doc, "") + self.assertIn("annotations", dir(appliance.__DABSchema__[field_name])) + self.assertEqual(appliance.__DABSchema__[field_name].annotations, expected_type) + self.assertIn("value", dir(appliance.__DABSchema__[field_name])) + self.assertEqual(appliance.__DABSchema__[field_name].value, expected_value) + self.assertIn("default_value", dir(appliance.__DABSchema__[field_name])) + self.assertEqual(appliance.__DABSchema__[field_name].default_value, expected_default_value) + self.assertIn("constraints", dir(appliance.__DABSchema__[field_name])) + self.assertEqual(appliance.__DABSchema__[field_name].constraints, ()) + + def test_immutable_fields_schema(self): + """Testing first appliance level, and Field types (annotated)""" + + # class can be created + class Appliance1(dm.BaseAppliance): + StrVar: "str" = "default value" + StrVar2: "str" = "default value2" + VarInt: "int" = 12 + VarInt2: "int" = 21 + VarFloat: "float" = 12.1 + VarFloat2: "float" = 21.2 + VarComplex: "complex" = complex(3, 5) + VarComplex2: "complex" = complex(8, 6) + VarBool: "bool" = True + VarBool2: "bool" = False + VarBytes: "bytes" = bytes.fromhex("2Ef0 F1f2 ") + VarBytes2: "bytes" = bytes.fromhex("2ff0 F7f2 ") + + app1 = Appliance1() + + self.assertIn("__DABSchema__", dir(app1)) + self.assertIn("__DABSchema__", app1.__dict__) + + self.check_immutable_fields_schema(app1, "StrVar", "default value", "default value", str) + self.check_immutable_fields_schema(app1, "StrVar2", "default value2", "default value2", str) + self.check_immutable_fields_schema(app1, "VarInt", 12, 12, int) + self.check_immutable_fields_schema(app1, "VarInt2", 21, 21, int) + self.check_immutable_fields_schema(app1, "VarFloat", 12.1, 12.1, float) + self.check_immutable_fields_schema(app1, "VarFloat2", 21.2, 21.2, float) + self.check_immutable_fields_schema(app1, "VarComplex", complex(3, 5), complex(3, 5), complex) + self.check_immutable_fields_schema(app1, "VarComplex2", complex(8, 6), complex(8, 6), complex) + self.check_immutable_fields_schema(app1, "VarBool", True, True, bool) + self.check_immutable_fields_schema(app1, "VarBool2", False, False, bool) + self.check_immutable_fields_schema(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes) + self.check_immutable_fields_schema(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("2ff0 F7f2 "), bytes) + + def test_container_field_schema(self): + """Testing first appliance level, and Field types (annotated)""" + + # class can be created + class Appliance1(dm.BaseAppliance): + ListStr: list[str] = ["val1", "val2"] + ListStr2: "list[str]" = ["val3", "val4"] + Dict1: dict[int, float] = {1: 1.1, 4: 7.6, 91: 23.6} + Dict2: "dict[str, str]" = {"1": "1.1", "4": "7.6", "91": "23.6"} + Tuple1: "tuple[str,...]" = ("a", "c") + Tuple2: tuple[str, ...] = ("a", "b") + FrozenSet1: frozenset[int] = frozenset({1, 2}) + FrozenSet2: "frozenset[int]" = frozenset({1, 2}) + Set1: set[int] = set({1, 2}) + Set2: "set[int]" = set({1, 2}) + + app1 = Appliance1() + + self.assertIn("__DABSchema__", dir(app1)) + self.assertIn("__DABSchema__", app1.__dict__) + + self.check_immutable_fields_schema(app1, "ListStr", ("val1", "val2"), ("val1", "val2"), list[str]) + self.check_immutable_fields_schema(app1, "ListStr2", ("val3", "val4"), ("val3", "val4"), list[str]) + self.check_immutable_fields_schema(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, dict[int, float]) + self.check_immutable_fields_schema( + app1, "Dict2", {"1": "1.1", "4": "7.6", "91": "23.6"}, {"1": "1.1", "4": "7.6", "91": "23.6"}, dict[str, str] + ) + self.check_immutable_fields_schema(app1, "Tuple1", ("a", "c"), ("a", "c"), tuple[str, ...]) + self.check_immutable_fields_schema(app1, "Tuple2", ("a", "b"), ("a", "b"), tuple[str, ...]) + self.check_immutable_fields_schema(app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), frozenset[int]) + self.check_immutable_fields_schema(app1, "FrozenSet2", frozenset({1, 2}), frozenset({1, 2}), frozenset[int]) + self.check_immutable_fields_schema(app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), set[int]) + self.check_immutable_fields_schema(app1, "Set2", frozenset({1, 2}), frozenset({1, 2}), set[int]) + + # same test with Typing types (list -> List ...) + # class can be created + class Appliance1(dm.BaseAppliance): + ListStr: List[str] = ["val1", "val2"] + ListStr2: "List[str]" = ["val3", "val4"] + Dict1: Dict[int, float] = {1: 1.1, 4: 7.6, 91: 23.6} + Dict2: "Dict[str, str]" = {"1": "1.1", "4": "7.6", "91": "23.6"} + Tuple1: "Tuple[str,...]" = ("a", "c") + Tuple2: Tuple[str, ...] = ("a", "b") + FrozenSet1: FrozenSet[int] = frozenset({1, 2}) + FrozenSet2: "FrozenSet[int]" = frozenset({1, 2}) + Set1: Set[int] = set({1, 2}) + Set2: "Set[int]" = set({1, 2}) + + app1 = Appliance1() + + self.assertIn("__DABSchema__", dir(app1)) + self.assertIn("__DABSchema__", app1.__dict__) + + self.check_immutable_fields_schema(app1, "ListStr", ("val1", "val2"), ("val1", "val2"), List[str]) + self.check_immutable_fields_schema(app1, "ListStr2", ("val3", "val4"), ("val3", "val4"), List[str]) + self.check_immutable_fields_schema(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, Dict[int, float]) + self.check_immutable_fields_schema( + app1, "Dict2", {"1": "1.1", "4": "7.6", "91": "23.6"}, {"1": "1.1", "4": "7.6", "91": "23.6"}, Dict[str, str] + ) + self.check_immutable_fields_schema(app1, "Tuple1", ("a", "c"), ("a", "c"), Tuple[str, ...]) + self.check_immutable_fields_schema(app1, "Tuple2", ("a", "b"), ("a", "b"), Tuple[str, ...]) + self.check_immutable_fields_schema(app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), FrozenSet[int]) + self.check_immutable_fields_schema(app1, "FrozenSet2", frozenset({1, 2}), frozenset({1, 2}), FrozenSet[int]) + self.check_immutable_fields_schema(app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), Set[int]) + self.check_immutable_fields_schema(app1, "Set2", frozenset({1, 2}), frozenset({1, 2}), Set[int]) + + def test_immutable_fields_annotated(self): + """Testing first appliance level, and Field types (annotated)""" + + # class can be created + class Appliance1(dm.BaseAppliance): + StrVar: Annotated[str, dm.DABFieldInfo(doc="foo1")] = "default value" + StrVar2: Annotated[str, dm.DABFieldInfo(doc="foo2")] = "default value2" + VarInt: Annotated[int, dm.DABFieldInfo(doc="foo3")] = 12 + VarInt2: Annotated[int, dm.DABFieldInfo(doc="foo4")] = 21 + VarFloat: Annotated[float, dm.DABFieldInfo(doc="foo5")] = 12.1 + VarFloat2: Annotated[float, dm.DABFieldInfo(doc="foo6")] = 21.2 + VarComplex: Annotated[complex, dm.DABFieldInfo(doc="foo7")] = complex(3, 5) + VarComplex2: Annotated[complex, dm.DABFieldInfo(doc="foo8")] = complex(8, 6) + VarBool: Annotated[bool, dm.DABFieldInfo(doc="foo9")] = True + VarBool2: Annotated[bool, dm.DABFieldInfo(doc="foo10")] = False + VarBytes: Annotated[bytes, dm.DABFieldInfo(doc="foo11")] = bytes.fromhex("2Ef0 F1f2 ") + VarBytes2: Annotated[bytes, dm.DABFieldInfo(doc="foo12")] = bytes.fromhex("2ff0 F7f2 ") + + app1 = Appliance1() + + self.immutable_vars__test_field(app1, "StrVar", "default value", "test") + self.assertEqual(app1.__DABSchema__["StrVar"].doc, "foo1") + self.immutable_vars__test_field(app1, "StrVar2", "default value2", "test2") + self.assertEqual(app1.__DABSchema__["StrVar2"].doc, "foo2") + self.immutable_vars__test_field(app1, "VarInt", 12, 13) + self.assertEqual(app1.__DABSchema__["VarInt"].doc, "foo3") + self.immutable_vars__test_field(app1, "VarInt2", 21, 22) + self.assertEqual(app1.__DABSchema__["VarInt2"].doc, "foo4") + self.immutable_vars__test_field(app1, "VarFloat", 12.1, 32) + self.assertEqual(app1.__DABSchema__["VarFloat"].doc, "foo5") + self.immutable_vars__test_field(app1, "VarFloat2", 21.2, 42) + self.assertEqual(app1.__DABSchema__["VarFloat2"].doc, "foo6") + self.immutable_vars__test_field(app1, "VarComplex", complex(3, 5), complex(1, 2)) + self.assertEqual(app1.__DABSchema__["VarComplex"].doc, "foo7") + self.immutable_vars__test_field(app1, "VarComplex2", complex(8, 6), complex(3, 2)) + self.assertEqual(app1.__DABSchema__["VarComplex2"].doc, "foo8") + self.immutable_vars__test_field(app1, "VarBool", True, False) + self.assertEqual(app1.__DABSchema__["VarBool"].doc, "foo9") + self.immutable_vars__test_field(app1, "VarBool2", False, True) + self.assertEqual(app1.__DABSchema__["VarBool2"].doc, "foo10") + self.immutable_vars__test_field(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("11f0 F1f2 ")) + self.assertEqual(app1.__DABSchema__["VarBytes"].doc, "foo11") + self.immutable_vars__test_field(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("11f0 F1e2 ")) + self.assertEqual(app1.__DABSchema__["VarBytes2"].doc, "foo12") + + with self.assertRaises(dm.InvalidFieldAnnotation): + + class _(dm.BaseAppliance): + _: Annotated[str, "foo2"] = "default value2" + + # annotation is parsed before the library can do anything, so the exception can only be TypeError + with self.assertRaises(TypeError): + + class _(dm.BaseAppliance): + _: Annotated[str] = "default value2" + + def test_immutable_fields_inheritance(self): + """Testing first appliance level, and Field types (simple)""" + + # class can be created + class Appliance1(dm.BaseAppliance): + StrVar: str = "default value" + StrVar2: str = "default value2" + VarInt: int = 12 + VarInt2: int = 21 + VarFloat: float = 12.1 + VarFloat2: float = 21.2 + VarComplex: complex = complex(3, 5) + VarComplex2: complex = complex(8, 6) + VarBool: bool = True + VarBool2: bool = False + VarBytes: bytes = bytes.fromhex("2Ef0 F1f2 ") + VarBytes2: bytes = bytes.fromhex("2ff0 F7f2 ") + + class Appliance2(Appliance1): + StrVar = "moded value" + StrVar2 = "moded value2" + VarInt = 54 + VarInt2 = 23 + VarFloat = 2.6 + VarFloat2 = 1.5 + VarComplex = complex(7, 1) + VarComplex2 = complex(3, 0) + VarBool = False + VarBool2 = True + VarBytes = bytes.fromhex("21f0 e1f2 ") + VarBytes2 = bytes.fromhex("2df0 F672 ") + + app1 = Appliance1() + + self.immutable_vars__test_field(app1, "StrVar", "default value", "test") + self.immutable_vars__test_field(app1, "StrVar2", "default value2", "test2") + self.immutable_vars__test_field(app1, "VarInt", 12, 13) + self.immutable_vars__test_field(app1, "VarInt2", 21, 22) + self.immutable_vars__test_field(app1, "VarFloat", 12.1, 32) + self.immutable_vars__test_field(app1, "VarFloat2", 21.2, 42) + self.immutable_vars__test_field(app1, "VarComplex", complex(3, 5), complex(1, 2)) + self.immutable_vars__test_field(app1, "VarComplex2", complex(8, 6), complex(3, 2)) + self.immutable_vars__test_field(app1, "VarBool", True, False) + self.immutable_vars__test_field(app1, "VarBool2", False, True) + self.immutable_vars__test_field(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("11f0 F1f2 ")) + self.immutable_vars__test_field(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("11f0 F1e2 ")) + + self.check_immutable_fields_schema(app1, "StrVar", "default value", "default value", str) + self.check_immutable_fields_schema(app1, "StrVar2", "default value2", "default value2", str) + self.check_immutable_fields_schema(app1, "VarInt", 12, 12, int) + self.check_immutable_fields_schema(app1, "VarInt2", 21, 21, int) + self.check_immutable_fields_schema(app1, "VarFloat", 12.1, 12.1, float) + self.check_immutable_fields_schema(app1, "VarFloat2", 21.2, 21.2, float) + self.check_immutable_fields_schema(app1, "VarComplex", complex(3, 5), complex(3, 5), complex) + self.check_immutable_fields_schema(app1, "VarComplex2", complex(8, 6), complex(8, 6), complex) + self.check_immutable_fields_schema(app1, "VarBool", True, True, bool) + self.check_immutable_fields_schema(app1, "VarBool2", False, False, bool) + self.check_immutable_fields_schema(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes) + self.check_immutable_fields_schema(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("2ff0 F7f2 "), bytes) + + app2 = Appliance2() + + self.immutable_vars__test_field(app2, "StrVar", "moded value", "test") + self.immutable_vars__test_field(app2, "StrVar2", "moded value2", "test2") + self.immutable_vars__test_field(app2, "VarInt", 54, 13) + self.immutable_vars__test_field(app2, "VarInt2", 23, 22) + self.immutable_vars__test_field(app2, "VarFloat", 2.6, 32) + self.immutable_vars__test_field(app2, "VarFloat2", 1.5, 42) + self.immutable_vars__test_field(app2, "VarComplex", complex(7, 1), complex(1, 2)) + self.immutable_vars__test_field(app2, "VarComplex2", complex(3, 0), complex(3, 2)) + self.immutable_vars__test_field(app2, "VarBool", False, False) + self.immutable_vars__test_field(app2, "VarBool2", True, True) + self.immutable_vars__test_field(app2, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("11f0 F1f2 ")) + self.immutable_vars__test_field(app2, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("11f0 F1e2 ")) + + self.check_immutable_fields_schema(app2, "StrVar", "moded value", "default value", str) + self.check_immutable_fields_schema(app2, "StrVar2", "moded value2", "default value2", str) + self.check_immutable_fields_schema(app2, "VarInt", 54, 12, int) + self.check_immutable_fields_schema(app2, "VarInt2", 23, 21, int) + self.check_immutable_fields_schema(app2, "VarFloat", 2.6, 12.1, float) + self.check_immutable_fields_schema(app2, "VarFloat2", 1.5, 21.2, float) + self.check_immutable_fields_schema(app2, "VarComplex", complex(7, 1), complex(3, 5), complex) + self.check_immutable_fields_schema(app2, "VarComplex2", complex(3, 0), complex(8, 6), complex) + self.check_immutable_fields_schema(app2, "VarBool", False, True, bool) + self.check_immutable_fields_schema(app2, "VarBool2", True, False, bool) + self.check_immutable_fields_schema(app2, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes) + self.check_immutable_fields_schema(app2, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("2ff0 F7f2 "), bytes) + + class Appliance3(Appliance2): + NewValue: str = "newval" + + self.assertNotIn("NewValue", Appliance2.__DABSchema__) + self.assertNotIn("NewValue", app2.__DABSchema__) + self.assertNotIn("NewValue", Appliance1.__DABSchema__) + self.assertNotIn("NewValue", app1.__DABSchema__) + + app3 = Appliance3() + self.immutable_vars__test_field(app3, "StrVar", "moded value", "test") + self.immutable_vars__test_field(app3, "StrVar2", "moded value2", "test2") + self.immutable_vars__test_field(app3, "VarInt", 54, 13) + self.immutable_vars__test_field(app3, "VarInt2", 23, 22) + self.immutable_vars__test_field(app3, "VarFloat", 2.6, 32) + self.immutable_vars__test_field(app3, "VarFloat2", 1.5, 42) + self.immutable_vars__test_field(app3, "VarComplex", complex(7, 1), complex(1, 2)) + self.immutable_vars__test_field(app3, "VarComplex2", complex(3, 0), complex(3, 2)) + self.immutable_vars__test_field(app3, "VarBool", False, False) + self.immutable_vars__test_field(app3, "VarBool2", True, True) + self.immutable_vars__test_field(app3, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("11f0 F1f2 ")) + self.immutable_vars__test_field(app3, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("11f0 F1e2 ")) + self.immutable_vars__test_field(app3, "NewValue", "newval", "test") + + self.check_immutable_fields_schema(app3, "StrVar", "moded value", "default value", str) + self.check_immutable_fields_schema(app3, "StrVar2", "moded value2", "default value2", str) + self.check_immutable_fields_schema(app3, "VarInt", 54, 12, int) + self.check_immutable_fields_schema(app3, "VarInt2", 23, 21, int) + self.check_immutable_fields_schema(app3, "VarFloat", 2.6, 12.1, float) + self.check_immutable_fields_schema(app3, "VarFloat2", 1.5, 21.2, float) + self.check_immutable_fields_schema(app3, "VarComplex", complex(7, 1), complex(3, 5), complex) + self.check_immutable_fields_schema(app3, "VarComplex2", complex(3, 0), complex(8, 6), complex) + self.check_immutable_fields_schema(app3, "VarBool", False, True, bool) + self.check_immutable_fields_schema(app3, "VarBool2", True, False, bool) + self.check_immutable_fields_schema(app3, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes) + self.check_immutable_fields_schema(app3, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("2ff0 F7f2 "), bytes) + self.check_immutable_fields_schema(app3, "NewValue", "newval", "newval", str) + + self.immutable_vars__test_field(app1, "StrVar", "default value", "test") + self.immutable_vars__test_field(app1, "StrVar2", "default value2", "test2") + self.immutable_vars__test_field(app1, "VarInt", 12, 13) + self.immutable_vars__test_field(app1, "VarInt2", 21, 22) + self.immutable_vars__test_field(app1, "VarFloat", 12.1, 32) + self.immutable_vars__test_field(app1, "VarFloat2", 21.2, 42) + self.immutable_vars__test_field(app1, "VarComplex", complex(3, 5), complex(1, 2)) + self.immutable_vars__test_field(app1, "VarComplex2", complex(8, 6), complex(3, 2)) + self.immutable_vars__test_field(app1, "VarBool", True, False) + self.immutable_vars__test_field(app1, "VarBool2", False, True) + self.immutable_vars__test_field(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("11f0 F1f2 ")) + self.immutable_vars__test_field(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("11f0 F1e2 ")) + + self.check_immutable_fields_schema(app1, "StrVar", "default value", "default value", str) + self.check_immutable_fields_schema(app1, "StrVar2", "default value2", "default value2", str) + self.check_immutable_fields_schema(app1, "VarInt", 12, 12, int) + self.check_immutable_fields_schema(app1, "VarInt2", 21, 21, int) + self.check_immutable_fields_schema(app1, "VarFloat", 12.1, 12.1, float) + self.check_immutable_fields_schema(app1, "VarFloat2", 21.2, 21.2, float) + self.check_immutable_fields_schema(app1, "VarComplex", complex(3, 5), complex(3, 5), complex) + self.check_immutable_fields_schema(app1, "VarComplex2", complex(8, 6), complex(8, 6), complex) + self.check_immutable_fields_schema(app1, "VarBool", True, True, bool) + self.check_immutable_fields_schema(app1, "VarBool2", False, False, bool) + self.check_immutable_fields_schema(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes) + self.check_immutable_fields_schema(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("2ff0 F7f2 "), bytes) + + self.immutable_vars__test_field(app2, "StrVar", "moded value", "test") + self.immutable_vars__test_field(app2, "StrVar2", "moded value2", "test2") + self.immutable_vars__test_field(app2, "VarInt", 54, 13) + self.immutable_vars__test_field(app2, "VarInt2", 23, 22) + self.immutable_vars__test_field(app2, "VarFloat", 2.6, 32) + self.immutable_vars__test_field(app2, "VarFloat2", 1.5, 42) + self.immutable_vars__test_field(app2, "VarComplex", complex(7, 1), complex(1, 2)) + self.immutable_vars__test_field(app2, "VarComplex2", complex(3, 0), complex(3, 2)) + self.immutable_vars__test_field(app2, "VarBool", False, False) + self.immutable_vars__test_field(app2, "VarBool2", True, True) + self.immutable_vars__test_field(app2, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("11f0 F1f2 ")) + self.immutable_vars__test_field(app2, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("11f0 F1e2 ")) + + self.check_immutable_fields_schema(app2, "StrVar", "moded value", "default value", str) + self.check_immutable_fields_schema(app2, "StrVar2", "moded value2", "default value2", str) + self.check_immutable_fields_schema(app2, "VarInt", 54, 12, int) + self.check_immutable_fields_schema(app2, "VarInt2", 23, 21, int) + self.check_immutable_fields_schema(app2, "VarFloat", 2.6, 12.1, float) + self.check_immutable_fields_schema(app2, "VarFloat2", 1.5, 21.2, float) + self.check_immutable_fields_schema(app2, "VarComplex", complex(7, 1), complex(3, 5), complex) + self.check_immutable_fields_schema(app2, "VarComplex2", complex(3, 0), complex(8, 6), complex) + self.check_immutable_fields_schema(app2, "VarBool", False, True, bool) + self.check_immutable_fields_schema(app2, "VarBool2", True, False, bool) + self.check_immutable_fields_schema(app2, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes) + self.check_immutable_fields_schema(app2, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("2ff0 F7f2 "), bytes) + + with self.assertRaises(dm.ReadOnlyFieldAnnotation): + + class _(Appliance1): + StrVar: int = 12 + + with self.assertRaises(dm.ReadOnlyFieldAnnotation): + + class _(Appliance3): + NewValue: int = 12 + + with self.assertRaises(dm.ReadOnlyFieldAnnotation): + + class _(Appliance3): + StrVar: int = 12 + # ---------- main ----------