Files
dabmodel/test/test_appliance.py
2025-09-22 01:14:14 +02:00

1914 lines
71 KiB
Python

# dabmodel (c) by chacha
#
# dabmodel is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
import unittest
import sys
import subprocess
from os import chdir, environ
from pathlib import Path
import textwrap
from typing import (
List,
Optional,
Dict,
Union,
Tuple,
Set,
FrozenSet,
Any,
Annotated,
)
import math
print(__name__)
print(__package__)
from src import dabmodel as dm
testdir_path = Path(__file__).parent.resolve()
chdir(testdir_path.parent.resolve())
def test_initializer_safe_testfc():
eval("print('hi')")
class ApplianceTest(unittest.TestCase):
def setUp(self):
print("\n->", unittest.TestCase.id(self))
def immutable_vars__test_field(self, obj: Any, name: str, default_value: Any, test_value: Any):
# field is not in the class
self.assertNotIn(name, dir(obj.__class__))
# field is in the object
self.assertIn(name, dir(obj))
# field is in the schema
self.assertIn(name, obj.__lam_schema__.keys())
# field is readable
self.assertEqual(getattr(obj, name), default_value)
# field is read only
with self.assertRaises(dm.ReadOnlyField):
setattr(obj, name, test_value)
def test_immutable_fields(self):
"""Testing first appliance level, and Field types (simple)"""
# class can be created
class Appliance1(dm.Appliance):
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.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 "),
)
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: str = 12
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: int = "value"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: float = "value"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: complex = "value"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: bool = "value"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: bytes = "value"
def test_annotation(self):
"""Testing first appliance level, and Field types (simple annotations)"""
# class can be created if annotation is a string
class Appliance1(dm.Appliance):
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.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 "),
)
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "str" = 12
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "int" = "value"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "float" = "value"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "complex" = "value"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "bool" = "value"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "bytes" = "value"
# class cannot be created if not annotated field
with self.assertRaises(dm.NotAnnotatedField):
class _(dm.Appliance):
test = "default value"
def test_optionnal(self):
"""Testing first appliance level, and Field types (Optionnal annotations)"""
# class can be created with Optionnal (and variant)
class Appliance1(dm.Appliance):
StrVar: Optional[str] = "default value"
StrVar2: Optional[str]
StrVar3: Union[None | str] = "default value"
StrVar4: Union[None | str]
StrVar5: None | str = "default value"
StrVar6: None | str
app1 = Appliance1()
self.immutable_vars__test_field(app1, "StrVar", "default value", "123")
self.immutable_vars__test_field(app1, "StrVar2", None, "123")
self.immutable_vars__test_field(app1, "StrVar3", "default value", "123")
self.immutable_vars__test_field(app1, "StrVar4", None, "123")
self.immutable_vars__test_field(app1, "StrVar5", "default value", "123")
self.immutable_vars__test_field(app1, "StrVar6", None, "123")
# class can be created with Optionnal (and variant), as string annotation
class Appliance2(dm.Appliance):
StrVar: "Optional[str]" = "default value"
StrVar2: "Optional[str]"
StrVar3: "Union[None | str]" = "default value"
StrVar4: "Union[None | str]"
StrVar5: "None | str" = "default value"
StrVar6: "None | str"
app2 = Appliance2()
self.immutable_vars__test_field(app2, "StrVar", "default value", "123")
self.immutable_vars__test_field(app2, "StrVar2", None, "123")
self.immutable_vars__test_field(app2, "StrVar3", "default value", "123")
self.immutable_vars__test_field(app2, "StrVar4", None, "123")
self.immutable_vars__test_field(app2, "StrVar5", "default value", "123")
self.immutable_vars__test_field(app2, "StrVar6", None, "123")
# @unittest.skip
def test_containers__set(self):
"""Testing first appliance level, and Field types (Set)"""
# class can be created with set
class Appliance1(dm.Appliance):
testVar: Set[int] = {1, 2}
testVar2: Set[str] = {"a", "b"}
testVar3: Set[float] = {0.5, 0.456, 12}
testVar4: "Set[str]" = {"a", "c"}
testVar5: set[str] = {"a", "b"}
testVar6: "set[str]" = {"a", "b"}
testVar7: set[int | str] = {1, 2, "abcd", "efg"}
app1 = Appliance1()
self.immutable_vars__test_field(app1, "testVar", {1, 2}, {1, 5})
self.assertEqual(app1.testVar, {2, 1})
self.immutable_vars__test_field(app1, "testVar2", {"a", "b"}, {"h", "c"})
self.assertEqual(app1.testVar2, {"b", "a"})
self.immutable_vars__test_field(app1, "testVar3", {0.5, 0.456, 12}, {0.9, 0.4156, 11})
self.assertEqual(app1.testVar3, {0.456, 0.5, 12})
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, "abcd", "efg"}, {"h", "c"})
# must work
sorted(app1.testVar)
with self.assertRaises(AttributeError):
app1.testVar.add(3)
with self.assertRaises(AttributeError):
app1.testVar4.add("coucou")
with self.assertRaises(dm.InvalidFieldValue):
class A(dm.Appliance):
a: Set[int] = {"a"}
with self.assertRaises(dm.InvalidFieldValue):
class B(dm.Appliance):
b: "Set[int]" = {"a"}
with self.assertRaises(dm.IncompletelyAnnotatedField):
class C(dm.Appliance):
c: Set = {1, 2}
with self.assertRaises(dm.IncompletelyAnnotatedField):
class D(dm.Appliance):
d: "Set" = {1, 2}
# Hacky part !
# set() are randomly ordered, and typeguard can be configured to only check 1st element of containers.
# we need to make sure it is properly configured, so we are expecting it to detect wrong type at any element
# this is why we need to run the code multiple time with a new interpreter that will use a different random seed
code = textwrap.dedent(
r"""
from src import dabmodel as dm
from typing import Set
try:
class Yours(dm.Appliance):
My1: Set[int] = {99, 1, 3, "a", 5,6}
except dm.InvalidFieldValue as ex:
raise SystemExit(2)
raise SystemExit(0)
"""
)
for i in range(15):
env = environ.copy()
env["PYTHONHASHSEED"] = str(i)
res = subprocess.run([sys.executable, "-c", code], env=env)
self.assertEqual(res.returncode, 2)
# @unittest.skip
def test_containers__frozenset(self):
"""Testing first appliance level, and Field types (FrozenSet)"""
# class can be created with set
class Appliance1(dm.Appliance):
testVar: frozenset[int] = frozenset({1, 2})
testVar2: frozenset[str] = frozenset({"a", "b"})
testVar3: frozenset[float] = frozenset({0.5, 0.456, 12})
testVar4: "frozenset[str]" = frozenset({"a", "c"})
testVar5: FrozenSet[int] = frozenset({1, 2})
testVar6: "FrozenSet[int]" = frozenset({1, 2})
testVar7: FrozenSet[int | str] = frozenset({1, 2, "abcd", "efg"})
app1 = Appliance1()
self.immutable_vars__test_field(app1, "testVar", {1, 2}, {1, 5})
self.assertEqual(app1.testVar, {2, 1})
self.immutable_vars__test_field(app1, "testVar2", {"a", "b"}, {"h", "c"})
self.assertEqual(app1.testVar2, {"b", "a"})
self.immutable_vars__test_field(app1, "testVar3", {0.5, 0.456, 12}, {0.9, 0.4156, 11})
self.assertEqual(app1.testVar3, {0.456, 0.5, 12})
self.immutable_vars__test_field(app1, "testVar4", {"a", "c"}, {"h", "e"})
self.immutable_vars__test_field(app1, "testVar5", {1, 2}, {1, 5})
self.immutable_vars__test_field(app1, "testVar6", {1, 2}, {1, 5})
self.immutable_vars__test_field(app1, "testVar7", {1, 2, "abcd", "efg"}, {1, 5})
# must work
sorted(app1.testVar)
with self.assertRaises(AttributeError):
app1.testVar.add(3)
with self.assertRaises(dm.InvalidFieldValue):
print("youhou")
class A(dm.Appliance):
a: FrozenSet[int] = {"a"}
with self.assertRaises(dm.InvalidFieldValue):
class B(dm.Appliance):
b: "FrozenSet[int]" = {"a"}
with self.assertRaises(dm.IncompletelyAnnotatedField):
class C(dm.Appliance):
c: FrozenSet = {1, 2}
with self.assertRaises(dm.IncompletelyAnnotatedField):
class D(dm.Appliance):
d: "FrozenSet" = {1, 2}
# Hacky part !
# set() are randomly ordered, and typeguard can be configured to only check 1st element of containers.
# we need to make sure it is properly configured, so we are expecting it to detect wrong type at any element
# this is why we need to run the code multiple time with a new interpreter that will use a different random seed
code = textwrap.dedent(
r"""
from src import dabmodel as dm
from typing import FrozenSet
try:
class Yours(dm.Appliance):
My1: FrozenSet[int] = frozenset({99, 1, 3, "a", 5,6})
except dm.InvalidFieldValue as ex:
raise SystemExit(2)
raise SystemExit(0)
"""
)
for i in range(15):
env = environ.copy()
env["PYTHONHASHSEED"] = str(i)
res = subprocess.run([sys.executable, "-c", code], env=env)
self.assertEqual(res.returncode, 2)
def test_containers__list(self):
"""Testing first appliance level, and Field types (List)"""
# class can be created with list
class Appliance1(dm.Appliance):
testVar: List[int] = [1, 2]
testVar2: List[str] = ["a", "b"]
testVar3: List[float] = [0.5, 0.456, 12]
testVar4: "List[str]" = ["a", "c"]
testVar5: list[str] = ["a", "b"]
testVar6: "list[str]" = ["a", "b"]
testVar7: List[Union[int, str]] = [1, 2, 3, "one", "two", "three"]
app1 = Appliance1()
# Note: lists are converted to tuples
self.immutable_vars__test_field(app1, "testVar", (1, 2), [1, 5])
self.immutable_vars__test_field(app1, "testVar2", ("a", "b"), ["h", "c"])
self.immutable_vars__test_field(app1, "testVar3", (0.5, 0.456, 12), [0.9, 0.4156, 11])
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"]
)
# must work
sorted(app1.testVar)
with self.assertRaises(AttributeError):
app1.testVar.append(3)
with self.assertRaises(AttributeError):
app1.testVar.pop()
with self.assertRaises(AttributeError):
app1.testVar.sort()
with self.assertRaises(AttributeError):
app1.testVar4.append("coucou")
with self.assertRaises(dm.InvalidFieldValue):
class E(dm.Appliance):
test: List[int] = ["a"]
with self.assertRaises(dm.InvalidFieldValue):
class F(dm.Appliance):
test: "List[int]" = ["a"]
with self.assertRaises(dm.IncompletelyAnnotatedField):
class G(dm.Appliance):
test: List = [1, 2]
with self.assertRaises(dm.IncompletelyAnnotatedField):
class H(dm.Appliance):
test: "List" = [1, 2]
def test_containers__dict(self):
"""Testing first appliance level, and Field types (Dict)"""
# class can be created with dict
class Appliance1(dm.Appliance):
testVar: Dict[int, str] = {1: "a", 2: "b"}
testVar2: "Dict[int, str]" = {1: "c", 99: "d"}
app1 = Appliance1()
self.immutable_vars__test_field(app1, "testVar", {1: "a", 2: "b"}, {1: "", 99: "i"})
self.immutable_vars__test_field(app1, "testVar2", {1: "c", 99: "d"}, {10: "", 50: "i"})
# TODO: wrap exception type
with self.assertRaises(TypeError):
app1.testVar[58] = "aaa"
# TODO: wrap exception type
with self.assertRaises(TypeError):
app1.testVar[1] = "ggg"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: Dict[int, str] = {1: 64, 2: "b"}
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "Dict[int, str]" = {1: 64, 2: "b"}
with self.assertRaises(dm.IncompletelyAnnotatedField):
class _(dm.Appliance):
test: Dict = {1: 64, 2: "b"}
# annotation is parsed before the library can do anything, so the exception can only be TypeError
with self.assertRaises(TypeError):
class _(dm.Appliance):
test: Dict[int] = {1: 64, 2: "b"}
# annotation is parsed before the library can do anything, so the exception can only be TypeError
with self.assertRaises(TypeError):
class _(dm.Appliance):
test: "Dict[int]" = {1: 64, 2: "b"}
def test_containers__tuple(self):
"""Testing first appliance level, and Field types (Tuple)"""
# class can be created with list
class Appliance1(dm.Appliance):
testVar: Tuple[int, ...] = (1, 2)
testVar2: Tuple[str, ...] = ("a", "b")
testVar3: Tuple[float, ...] = (0.5, 0.456, 12)
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()
self.immutable_vars__test_field(app1, "testVar", (1, 2), (1, 5))
self.immutable_vars__test_field(app1, "testVar2", ("a", "b"), ("h", "c"))
self.immutable_vars__test_field(app1, "testVar3", (0.5, 0.456, 12), (0.9, 0.4156, 11))
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, "b"), (7, "h"))
# must work
sorted(app1.testVar)
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: Tuple[int] = "a"
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "Tuple[int]" = "a"
with self.assertRaises(dm.IncompletelyAnnotatedField):
class _(dm.Appliance):
test: Tuple = (1, 2)
with self.assertRaises(dm.IncompletelyAnnotatedField):
class _(dm.Appliance):
test: "Tuple" = (1, 2)
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "Tuple[int,...]" = (1, "a")
with self.assertRaises(dm.InvalidFieldValue):
class _(dm.Appliance):
test: "tuple[int,...]" = (1, "a")
def check_immutable_fields_schema(
self,
appliance: dm.Appliance,
field_name: str,
expected_value: str,
expected_default_value: str,
expected_type: type,
):
self.assertIn(field_name, appliance.__lam_schema__)
self.assertIn("doc", dir(appliance.__lam_schema__[field_name]))
self.assertEqual(appliance.__lam_schema__[field_name].doc, "")
self.assertIn("annotations", dir(appliance.__lam_schema__[field_name]))
self.assertEqual(appliance.__lam_schema__[field_name].annotations, expected_type)
self.assertIn("value", dir(appliance.__lam_schema__[field_name]))
self.assertEqual(appliance.__lam_schema__[field_name].value, expected_value)
self.assertIn("default_value", dir(appliance.__lam_schema__[field_name]))
self.assertEqual(appliance.__lam_schema__[field_name].default_value, expected_default_value)
self.assertIn("constraints", dir(appliance.__lam_schema__[field_name]))
self.assertEqual(appliance.__lam_schema__[field_name].constraints, ())
def test_immutable_fields_schema(self):
"""Testing first appliance level, and Field types (annotated)"""
# class can be created
class Appliance1(dm.Appliance):
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("__lam_schema__", dir(app1))
self.assertIn("__lam_schema__", 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.Appliance):
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("__lam_schema__", dir(app1))
self.assertIn("__lam_schema__", 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.Appliance):
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("__lam_schema__", dir(app1))
self.assertIn("__lam_schema__", 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.Appliance):
StrVar: Annotated[str, dm.LAMFieldInfo(doc="foo1")] = "default value"
StrVar2: Annotated[str, dm.LAMFieldInfo(doc="foo2")] = "default value2"
VarInt: Annotated[int, dm.LAMFieldInfo(doc="foo3")] = 12
VarInt2: Annotated[int, dm.LAMFieldInfo(doc="foo4")] = 21
VarFloat: Annotated[float, dm.LAMFieldInfo(doc="foo5")] = 12.1
VarFloat2: Annotated[float, dm.LAMFieldInfo(doc="foo6")] = 21.2
VarComplex: Annotated[complex, dm.LAMFieldInfo(doc="foo7")] = complex(3, 5)
VarComplex2: Annotated[complex, dm.LAMFieldInfo(doc="foo8")] = complex(8, 6)
VarBool: Annotated[bool, dm.LAMFieldInfo(doc="foo9")] = True
VarBool2: Annotated[bool, dm.LAMFieldInfo(doc="foo10")] = False
VarBytes: Annotated[bytes, dm.LAMFieldInfo(doc="foo11")] = bytes.fromhex("2Ef0 F1f2 ")
VarBytes2: Annotated[bytes, dm.LAMFieldInfo(doc="foo12")] = bytes.fromhex("2ff0 F7f2 ")
app1 = Appliance1()
self.immutable_vars__test_field(app1, "StrVar", "default value", "test")
self.assertEqual(app1.__lam_schema__["StrVar"].doc, "foo1")
self.immutable_vars__test_field(app1, "StrVar2", "default value2", "test2")
self.assertEqual(app1.__lam_schema__["StrVar2"].doc, "foo2")
self.immutable_vars__test_field(app1, "VarInt", 12, 13)
self.assertEqual(app1.__lam_schema__["VarInt"].doc, "foo3")
self.immutable_vars__test_field(app1, "VarInt2", 21, 22)
self.assertEqual(app1.__lam_schema__["VarInt2"].doc, "foo4")
self.immutable_vars__test_field(app1, "VarFloat", 12.1, 32)
self.assertEqual(app1.__lam_schema__["VarFloat"].doc, "foo5")
self.immutable_vars__test_field(app1, "VarFloat2", 21.2, 42)
self.assertEqual(app1.__lam_schema__["VarFloat2"].doc, "foo6")
self.immutable_vars__test_field(app1, "VarComplex", complex(3, 5), complex(1, 2))
self.assertEqual(app1.__lam_schema__["VarComplex"].doc, "foo7")
self.immutable_vars__test_field(app1, "VarComplex2", complex(8, 6), complex(3, 2))
self.assertEqual(app1.__lam_schema__["VarComplex2"].doc, "foo8")
self.immutable_vars__test_field(app1, "VarBool", True, False)
self.assertEqual(app1.__lam_schema__["VarBool"].doc, "foo9")
self.immutable_vars__test_field(app1, "VarBool2", False, True)
self.assertEqual(app1.__lam_schema__["VarBool2"].doc, "foo10")
self.immutable_vars__test_field(
app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("11f0 F1f2 ")
)
self.assertEqual(app1.__lam_schema__["VarBytes"].doc, "foo11")
self.immutable_vars__test_field(
app1,
"VarBytes2",
bytes.fromhex("2ff0 F7f2 "),
bytes.fromhex("11f0 F1e2 "),
)
self.assertEqual(app1.__lam_schema__["VarBytes2"].doc, "foo12")
with self.assertRaises(dm.InvalidFieldAnnotation):
class _(dm.Appliance):
test: 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.Appliance):
test: 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.Appliance):
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.__lam_schema__)
self.assertNotIn("NewValue", app2.__lam_schema__)
self.assertNotIn("NewValue", Appliance1.__lam_schema__)
self.assertNotIn("NewValue", app1.__lam_schema__)
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
def test_containers_field_inheritance(self):
"""Testing first appliance level, and Field types (annotated)"""
# class can be created
class Appliance1(dm.Appliance):
ListStr: list[str] = ["val1", "val2"]
Dict1: dict[int, float] = {1: 1.1, 4: 7.6, 91: 23.6}
Tuple1: "tuple[str,...]" = ("a", "c")
FrozenSet1: frozenset[int] = frozenset({1, 2})
Set1: set[int] = set({1, 2})
# class can be created
class Appliance2(Appliance1):
ListStr = ["mod val1", "mod val2"]
Dict1 = {4: 1.1, 9: 7.6, 51: 23.6}
Tuple1 = ("aa", "cc")
FrozenSet1 = frozenset({14, 27})
Set1 = set({1, 20})
app1 = Appliance1()
self.immutable_vars__test_field(app1, "ListStr", ("val1", "val2"), ["val2", "val3"])
self.immutable_vars__test_field(
app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}
)
self.immutable_vars__test_field(app1, "Tuple1", ("a", "c"), ("h", "r"))
self.immutable_vars__test_field(app1, "FrozenSet1", frozenset({1, 2}), frozenset({4, 0}))
self.immutable_vars__test_field(app1, "Set1", frozenset({1, 2}), set({4, 0}))
self.check_immutable_fields_schema(
app1, "ListStr", ("val1", "val2"), ("val1", "val2"), 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, "Tuple1", ("a", "c"), ("a", "c"), tuple[str, ...])
self.check_immutable_fields_schema(
app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), frozenset[int]
)
self.check_immutable_fields_schema(
app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), set[int]
)
app2 = Appliance2()
self.immutable_vars__test_field(app2, "ListStr", ("mod val1", "mod val2"), ["val2", "val3"])
self.immutable_vars__test_field(
app2, "Dict1", {4: 1.1, 9: 7.6, 51: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}
)
self.immutable_vars__test_field(app2, "Tuple1", ("aa", "cc"), ("h", "r"))
self.immutable_vars__test_field(app2, "FrozenSet1", frozenset({14, 27}), frozenset({4, 0}))
self.immutable_vars__test_field(app2, "Set1", frozenset({1, 20}), set({4, 0}))
self.check_immutable_fields_schema(
app2, "ListStr", ("mod val1", "mod val2"), ("val1", "val2"), list[str]
)
self.check_immutable_fields_schema(
app2,
"Dict1",
{4: 1.1, 9: 7.6, 51: 23.6},
{1: 1.1, 4: 7.6, 91: 23.6},
dict[int, float],
)
self.check_immutable_fields_schema(
app2, "Tuple1", ("aa", "cc"), ("a", "c"), tuple[str, ...]
)
self.check_immutable_fields_schema(
app2, "FrozenSet1", frozenset({14, 27}), frozenset({1, 2}), frozenset[int]
)
self.check_immutable_fields_schema(
app2, "Set1", frozenset({1, 20}), frozenset({1, 2}), set[int]
)
self.immutable_vars__test_field(app1, "ListStr", ("val1", "val2"), ["val2", "val3"])
self.immutable_vars__test_field(
app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}
)
self.immutable_vars__test_field(app1, "Tuple1", ("a", "c"), ("h", "r"))
self.immutable_vars__test_field(app1, "FrozenSet1", frozenset({1, 2}), frozenset({4, 0}))
self.immutable_vars__test_field(app1, "Set1", frozenset({1, 2}), set({4, 0}))
self.check_immutable_fields_schema(
app1, "ListStr", ("val1", "val2"), ("val1", "val2"), 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, "Tuple1", ("a", "c"), ("a", "c"), tuple[str, ...])
self.check_immutable_fields_schema(
app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), frozenset[int]
)
self.check_immutable_fields_schema(
app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), set[int]
)
# class can be created
class Appliance3(Appliance2):
ListStr2: list[str] = ["mod val3", "mod val3"]
Dict2: dict[int, float] = {9: 8.1, 5: 98.6, 551: 3.6}
Tuple2: "tuple[str,...]" = ("aaa", "ccc")
FrozenSet2: frozenset[int] = frozenset({114, 127})
Set2: set[int] = set({10, 250})
app3 = Appliance3()
self.immutable_vars__test_field(app3, "ListStr", ("mod val1", "mod val2"), ["val2", "val3"])
self.immutable_vars__test_field(
app3, "Dict1", {4: 1.1, 9: 7.6, 51: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}
)
self.immutable_vars__test_field(app3, "Tuple1", ("aa", "cc"), ("h", "r"))
self.immutable_vars__test_field(app3, "FrozenSet1", frozenset({14, 27}), frozenset({4, 0}))
self.immutable_vars__test_field(app3, "Set1", frozenset({1, 20}), set({4, 0}))
self.immutable_vars__test_field(
app3, "ListStr2", ("mod val3", "mod val3"), ["mod val3", "mod val3"]
)
self.immutable_vars__test_field(
app3, "Dict2", {9: 8.1, 5: 98.6, 551: 3.6}, {9: 8.1, 5: 98.6, 551: 3.6}
)
self.immutable_vars__test_field(app3, "Tuple2", ("aaa", "ccc"), ("aaa", "ccc"))
self.immutable_vars__test_field(
app3, "FrozenSet2", frozenset({114, 127}), frozenset({114, 127})
)
self.immutable_vars__test_field(app3, "Set2", frozenset({10, 250}), set({10, 250}))
self.check_immutable_fields_schema(
app3, "ListStr", ("mod val1", "mod val2"), ("val1", "val2"), list[str]
)
self.check_immutable_fields_schema(
app3,
"Dict1",
{4: 1.1, 9: 7.6, 51: 23.6},
{1: 1.1, 4: 7.6, 91: 23.6},
dict[int, float],
)
self.check_immutable_fields_schema(
app3, "Tuple1", ("aa", "cc"), ("a", "c"), tuple[str, ...]
)
self.check_immutable_fields_schema(
app3, "FrozenSet1", frozenset({14, 27}), frozenset({1, 2}), frozenset[int]
)
self.check_immutable_fields_schema(
app3, "Set1", frozenset({1, 20}), frozenset({1, 2}), set[int]
)
self.check_immutable_fields_schema(
app3,
"ListStr2",
("mod val3", "mod val3"),
("mod val3", "mod val3"),
list[str],
)
self.check_immutable_fields_schema(
app3,
"Dict2",
{9: 8.1, 5: 98.6, 551: 3.6},
{9: 8.1, 5: 98.6, 551: 3.6},
dict[int, float],
)
self.check_immutable_fields_schema(
app3, "Tuple2", ("aaa", "ccc"), ("aaa", "ccc"), tuple[str, ...]
)
self.check_immutable_fields_schema(
app3,
"FrozenSet2",
frozenset({114, 127}),
frozenset({114, 127}),
frozenset[int],
)
self.check_immutable_fields_schema(
app3, "Set2", frozenset({10, 250}), frozenset({10, 250}), set[int]
)
self.immutable_vars__test_field(app2, "ListStr", ("mod val1", "mod val2"), ["val2", "val3"])
self.immutable_vars__test_field(
app2, "Dict1", {4: 1.1, 9: 7.6, 51: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}
)
self.immutable_vars__test_field(app2, "Tuple1", ("aa", "cc"), ("h", "r"))
self.immutable_vars__test_field(app2, "FrozenSet1", frozenset({14, 27}), frozenset({4, 0}))
self.immutable_vars__test_field(app2, "Set1", frozenset({1, 20}), set({4, 0}))
self.check_immutable_fields_schema(
app2, "ListStr", ("mod val1", "mod val2"), ("val1", "val2"), list[str]
)
self.check_immutable_fields_schema(
app2,
"Dict1",
{4: 1.1, 9: 7.6, 51: 23.6},
{1: 1.1, 4: 7.6, 91: 23.6},
dict[int, float],
)
self.check_immutable_fields_schema(
app2, "Tuple1", ("aa", "cc"), ("a", "c"), tuple[str, ...]
)
self.check_immutable_fields_schema(
app2, "FrozenSet1", frozenset({14, 27}), frozenset({1, 2}), frozenset[int]
)
self.check_immutable_fields_schema(
app2, "Set1", frozenset({1, 20}), frozenset({1, 2}), set[int]
)
self.immutable_vars__test_field(app1, "ListStr", ("val1", "val2"), ["val2", "val3"])
self.immutable_vars__test_field(
app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}
)
self.immutable_vars__test_field(app1, "Tuple1", ("a", "c"), ("h", "r"))
self.immutable_vars__test_field(app1, "FrozenSet1", frozenset({1, 2}), frozenset({4, 0}))
self.immutable_vars__test_field(app1, "Set1", frozenset({1, 2}), set({4, 0}))
self.check_immutable_fields_schema(
app1, "ListStr", ("val1", "val2"), ("val1", "val2"), 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, "Tuple1", ("a", "c"), ("a", "c"), tuple[str, ...])
self.check_immutable_fields_schema(
app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), frozenset[int]
)
self.check_immutable_fields_schema(
app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), set[int]
)
def test_initializer(self):
"""Testing first appliance level, and Field types (simple)"""
# class can be created
class Appliance1(dm.Appliance):
VarInt: int = 12
VarInt2: int = 21
list1: list[int] = [1]
set1: set[float] = {1.43}
set2: set[float] = {5.43}
VarStr1: str = "abcd"
@classmethod
def __initializer(cls):
cls.VarInt2 = cls.VarInt + 1
cls.list1.append(2)
cls.set2 = cls.set1 | {1234}
cls.set1 = {0}
cls.VarStr1 = cls.VarStr1.upper()
app1 = Appliance1()
self.assertEqual(app1.VarInt, 12)
self.assertEqual(app1.VarInt2, 13)
self.assertEqual(app1.set1, frozenset({0}))
self.assertEqual(app1.set2, frozenset((1.43, 1234)))
self.assertEqual(app1.VarStr1, "ABCD")
# class can be created
class _(dm.Appliance):
VarInt: int = 41
@classmethod
def __initializer(cls):
cls.VarInt = cls.VarInt + 1
app2 = _()
self.assertEqual(app2.VarInt, 42)
def test_initializer_dont_leak(self):
class A(dm.Appliance):
VarInt: int = 12
class B(A):
@classmethod
def __initializer(cls):
cls.VarInt = cls.VarInt + 1
a = A()
b = B()
self.assertEqual(a.VarInt, 12)
self.assertEqual(b.VarInt, 13)
def test_initializer_safe(self):
with self.assertRaises(dm.ImportForbidden):
class test(dm.Appliance):
my: int = 0
@classmethod
def __initializer(cls):
import pprint
with self.assertRaises(NameError):
class _(dm.Appliance):
my: int = 0
@classmethod
def __initializer(cls):
eval("2 ** 8")
with self.assertRaises(NameError):
class _(dm.Appliance):
my: int = 0
@classmethod
def __initializer(cls):
open("foo")
with self.assertRaises(NameError):
class _(dm.Appliance):
my: int = 0
@classmethod
def __initializer(cls):
exec("exit()")
with self.assertRaises(NameError):
class _(dm.Appliance):
my: int = 0
@classmethod
def __initializer(cls):
compile("print(55)", "test", "eval")
with self.assertRaises(NameError):
class _(dm.Appliance):
my: int = 0
@classmethod
def __initializer(cls):
compile("print(55)", "test", "eval")
def testfc():
eval("print('test')")
with self.assertRaises(dm.FunctionForbidden):
class _(dm.Appliance):
my: int = 0
@classmethod
def __initializer(cls):
testfc()
with self.assertRaises(dm.FunctionForbidden):
def test_func_int() -> int:
return 12
class _(dm.Appliance):
my: int = 0
@classmethod
def __initializer(cls):
cls.my = test_func_int()
with self.assertRaises(dm.FunctionForbidden):
class _(dm.Appliance):
my: int = 0
@classmethod
def __initializer(cls):
if True:
testfc()
# class can be created
class Appliance2(dm.Appliance):
VarInt: int = -56
VarInt2: int = 0
VarInt3: int = 0
VarInt4: int = 0
VarInt5: int = 0
VarFloat: float = 1.23
list1: list[int] = [1, 2, 0, 4]
list2: Optional[list[int]] = None
list3: Optional[list[int]] = None
set1: set[float] = {1.43}
set2: set[float] = {5.43}
@classmethod
def __initializer(cls):
cls.VarInt2 = abs(cls.VarInt)
cls.VarFloat = round(cls.VarFloat)
cls.VarInt = min(cls.list1)
cls.VarInt3 = max(cls.list1)
cls.VarInt4 = sum(cls.list1)
cls.VarInt5 = len(cls.list1)
cls.list2 = sorted(cls.list1)
cls.list3 = []
for i in range(5):
cls.list3.append(i)
cls.set2 = {math.ceil(list(cls.set1)[0])}
app2 = Appliance2()
self.assertEqual(app2.VarInt2, 56)
self.assertEqual(app2.VarFloat, 1)
self.assertEqual(app2.VarInt, 0)
self.assertEqual(app2.VarInt3, 4)
self.assertEqual(app2.VarInt4, 7)
self.assertEqual(app2.VarInt5, 4)
self.assertEqual(app2.list2, (0, 1, 2, 4))
self.assertEqual(app2.list3, (0, 1, 2, 3, 4))
self.assertEqual(app2.set2, {2})
with self.assertRaises(NameError):
class _(dm.Appliance):
_: int = 0
@classmethod
def __initializer(cls):
test_initializer_safe_testfc()
def test_inheritance_chain(self):
# class can be created
class Appliance1(dm.Appliance):
VarStr: str = "testvalue1"
class Appliance2(Appliance1):
pass
class Appliance3(Appliance2):
pass
app1 = Appliance1()
app2 = Appliance2()
app3 = Appliance3()
self.assertEqual(app1.VarStr, "testvalue1")
self.assertEqual(app2.VarStr, "testvalue1")
self.assertEqual(app3.VarStr, "testvalue1")
class Appliance4(Appliance3):
VarStr = "testvalue moded"
app4 = Appliance4()
self.assertEqual(app1.VarStr, "testvalue1")
self.assertEqual(app2.VarStr, "testvalue1")
self.assertEqual(app3.VarStr, "testvalue1")
self.assertEqual(app4.VarStr, "testvalue moded")
app1b = Appliance1()
app2b = Appliance2()
app3b = Appliance3()
self.assertEqual(app1b.VarStr, "testvalue1")
self.assertEqual(app2b.VarStr, "testvalue1")
self.assertEqual(app3b.VarStr, "testvalue1")
def test_override(self):
"""Testing first appliance level, and Field types (List)"""
# class can be created with list
class Appliance1(dm.Appliance):
testVar: List[int] = [1, 2]
testVar2: List[str] = ["a", "b"]
testVar3: float = 0.5
testVar4: "List[str]" = ["a", "c"]
testVar5: list[str] = ["a", "b"]
testVar6: "list[str]" = ["a", "b"]
app1 = Appliance1(testVar=[5, 6], testVar2=[], testVar3=45.8)
self.immutable_vars__test_field(app1, "testVar", (5, 6), (1, 5))
self.immutable_vars__test_field(app1, "testVar2", (), ("h", "c"))
self.immutable_vars__test_field(app1, "testVar3", 45.8, 43.9)
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"))
# test no leaks
app2 = Appliance1()
self.immutable_vars__test_field(app2, "testVar", (1, 2), (1, 5))
self.immutable_vars__test_field(app2, "testVar2", ("a", "b"), ("h", "c"))
self.immutable_vars__test_field(app2, "testVar3", 0.5, 43.9)
self.immutable_vars__test_field(app2, "testVar4", ("a", "c"), ("h", "e"))
self.immutable_vars__test_field(app2, "testVar5", ("a", "b"), ("h", "c"))
self.immutable_vars__test_field(app2, "testVar6", ("a", "b"), ("h", "c"))
def test_new_field_forbidden(self):
class App(dm.Appliance):
x: int = 1
app = App()
with self.assertRaises(dm.NewFieldForbidden):
app.y = 2
def test_private_field_allowed(self):
class App(dm.Appliance):
x: int = 1
app = App()
app._internal = 42 # should be allowed
self.assertEqual(app._internal, 42)
def test_field_cannot_be_reassigned(self):
class App(dm.Appliance):
x: int = 1
app = App()
with self.assertRaises(dm.ReadOnlyField):
app.x = 99
def test_schema_fields_are_frozen(self):
class App(dm.Appliance):
x: list[int] = [1, 2]
app = App()
with self.assertRaises(AttributeError): # frozendict / tuple immutability
app.x.append(3)
with self.assertRaises(dm.ReadOnlyField): # frozendict / tuple immutability
app.x = (1, 3)
def test_initializer_invalid_type_raises(self):
with self.assertRaises(dm.InvalidFieldValue):
class App(dm.Appliance):
x: int = 1
@classmethod
def __initializer(cls):
cls.x = "wrong" # wrong type
def test_initializer_forbid_nested_functions(self):
with self.assertRaises(dm.FunctionForbidden):
class App(dm.Appliance):
x: int = 1
@classmethod
def __initializer(cls):
def inner():
return 2
cls.x = inner()
with self.assertRaises(dm.FunctionForbidden):
class _(dm.Appliance):
x: int = 1
@classmethod
def __initializer(cls):
y = 2
def inner():
return y + 1
cls.x = inner()
with self.assertRaises(dm.FunctionForbidden):
class _(dm.Appliance):
x: int = 1
@classmethod
def __initializer(cls):
y = 2
def inner():
return y + 1
cls.x = lambda: inner()
def test_initializer_lambda_forbidden(self):
class App(dm.Appliance):
x: int = 1
@classmethod
def __initializer(cls):
f = lambda: 42
cls.x = f()
def test_multiple_inheritance_forbidden(self):
with self.assertRaises(dm.MultipleInheritanceForbidden):
class A(dm.Appliance):
pass
class B(dm.Appliance):
pass
class C(A, B):
pass
def test_initializer_can_use_math(self):
class App(dm.Appliance):
val: float = 0.0
@classmethod
def __initializer(cls):
# should be able to access math without NameError
cls.val = math.ceil(1.2) + math.floor(2.8)
app = App()
# math.ceil(1.2)=2, math.floor(2.8)=2 → 4
self.assertEqual(app.val, 4)
def test_deepfreeze_nested_mixed_tuple_list(self):
class App(dm.Appliance):
data: tuple[list[int], tuple[int, list[int]]] = ([1, 2], (3, [4, 5]))
app = App()
# Top-level: must be tuple
self.assertIsInstance(app.data, tuple)
# First element of tuple: should have been frozen to tuple, not list
self.assertIsInstance(app.data[0], tuple)
# Nested second element: itself a tuple
self.assertIsInstance(app.data[1], tuple)
# Deepest element: inner list should also be frozen to tuple
self.assertIsInstance(app.data[1][1], tuple)
# Check immutability
with self.assertRaises(TypeError):
app.data[0] += (99,) # tuples are immutable
with self.assertRaises(TypeError):
app.data[1][1] += (42,) # inner tuple also immutable
def test_inacurate_type(self):
with self.assertRaises(dm.InvalidFieldAnnotation):
class Appliance1(dm.Appliance):
SomeVar: list = []
with self.assertRaises(dm.InvalidFieldAnnotation):
class Appliance2(dm.Appliance):
SomeVar: list[Any] = []
with self.assertRaises(dm.InvalidFieldAnnotation):
class Appliance3(dm.Appliance):
SomeVar: list[object] = []
with self.assertRaises(dm.InvalidFieldAnnotation):
class Appliance4(dm.Appliance):
SomeVar: dict = {}
with self.assertRaises(dm.InvalidFieldAnnotation):
class Appliance5(dm.Appliance):
SomeVar: dict[str, Any] = {}
with self.assertRaises(dm.InvalidFieldAnnotation):
class Appliance6(dm.Appliance):
SomeVar: dict[Any, Any] = {}
with self.assertRaises(dm.InvalidFieldAnnotation):
class Appliance7(dm.Appliance):
SomeVar: dict[Any, str] = {}
with self.assertRaises(dm.InvalidFieldAnnotation):
class Appliance8(dm.Appliance):
SomeVar: dict[str, object] = {}
def test_deepfreeze_nested_dict_list_set(self):
class App(dm.Appliance):
data: dict[str, list[int] | set[str] | dict[str, list[int] | set[str]]] = {
"numbers": [1, 2, 3],
"letters": {"a", "b", "c"},
"mixed": {"x": [4, 5], "y": {"z"}},
}
app = App()
# Top-level: should be frozendict
self.assertEqual(type(app.data).__name__, "frozendict")
# Lists must be frozen to tuple
self.assertIsInstance(app.data["numbers"], tuple)
self.assertIsInstance(app.data["mixed"]["x"], tuple)
# Sets must be frozen to frozenset
self.assertIsInstance(app.data["letters"], frozenset)
self.assertIsInstance(app.data["mixed"]["y"], frozenset)
# Check immutability
with self.assertRaises(TypeError):
app.data["numbers"] += (99,)
with self.assertRaises(AttributeError):
app.data["letters"].add("d")
with self.assertRaises(TypeError):
app.data["mixed"]["x"] += (6,)
def test_unknown_parameters_raise(self):
class App(dm.Appliance):
a: int = 1
with self.assertRaises(dm.InvalidFieldValue) as cm:
App(foo=123)
self.assertIn("Unknown parameters:", str(cm.exception))
self.assertIn("foo", str(cm.exception))
def test_variadic_tuple_annotation_ok_and_errors(self):
class A(dm.Appliance):
xs: tuple[int, ...] = (1, 2, 3)
self.assertEqual(A().xs, (1, 2, 3))
class B(dm.Appliance):
xs: "Tuple[int, ...]" = (4, 5)
self.assertEqual(B().xs, (4, 5))
# wrong inner type should fail
with self.assertRaises(dm.InvalidFieldValue):
class _Bad(dm.Appliance):
xs: tuple[int, ...] = (1, "x")
def test_field_override_typechecked_and_instance_local(self):
class App(dm.Appliance):
a: list[int] = [1]
app1 = App(a=[9])
app2 = App()
# deepfreeze: lists become tuples
self.assertEqual(app1.a, (9,))
self.assertEqual(app2.a, (1,)) # no leakage
with self.assertRaises(dm.InvalidFieldValue):
App(a=["oops"]) # wrong inner type
def test_root_field_override_nonexisting_rejected(self):
class App(dm.Appliance):
x: int = 1
with self.assertRaises(dm.InvalidFieldValue):
App(y=2) # not in schema -> unknown parameter
def test_dict_field_override_with_nested_containers(self):
class App(dm.Appliance):
data: dict[str, list[int]] = {"numbers": [1, 2]}
app = App(data={"numbers": [9, 8]})
# Dict must be frozen, lists inside must become tuples
self.assertEqual(type(app.data).__name__, "frozendict")
self.assertIsInstance(app.data["numbers"], tuple)
self.assertEqual(app.data["numbers"], (9, 8))
# Wrong type inside list should raise
with self.assertRaises(dm.InvalidFieldValue):
App(data={"numbers": [1, "oops"]})
def test_initializer_modifies_nested_containers(self):
class App(dm.Appliance):
data: dict[str, list[int]] = {"nums": [1]}
@classmethod
def __initializer(cls):
# Append into nested list
cls.data["nums"].append(2)
app = App()
self.assertEqual(app.data["nums"], (1, 2)) # frozen tuple after init
# ---------- main ----------
if __name__ == "__main__":
unittest.main()