diff --git a/src/dabmodel/__init__.py b/src/dabmodel/__init__.py index ef31d9a..a39d6eb 100644 --- a/src/dabmodel/__init__.py +++ b/src/dabmodel/__init__.py @@ -30,4 +30,5 @@ from .model import ( FunctionForbidden, FrozenDABField, InvalidFeatureInheritance, + FeatureNotBound, ) diff --git a/src/dabmodel/model.py b/src/dabmodel/model.py index 71d8057..8610ee3 100644 --- a/src/dabmodel/model.py +++ b/src/dabmodel/model.py @@ -21,7 +21,7 @@ from typing import ( Callable, Type, ) -from types import UnionType, FunctionType, SimpleNamespace +from types import UnionType, FunctionType, SimpleNamespace, MethodType from copy import deepcopy, copy # from pprint import pprint @@ -151,6 +151,10 @@ class FunctionForbidden(DABModelException): function call are forbidden """ +class FeatureNotBound(DABModelException): + """FeatureNotBound Exception class + a Feature must be bound to an Appliance + """ ALLOWED_HELPERS_MATH = SimpleNamespace( sqrt=math.sqrt, @@ -516,10 +520,12 @@ class BaseMeta(type): initializer_name = "__initializer" else: initializer_name = f"_{name}__initializer" - elif _fname.startswith("__"): + elif _fname.startswith("_"): + pass + elif isinstance(_fvalue, classmethod): pass else: - # print(f"Parsing Field: {_fname} / {_fvalue}") + print(f"Parsing Field: {_fname} / {_fvalue}") if len(bases) == 1 and _fname in namespace["__DABSchema__"].keys(): # Modified fields mcs.pre_processing_modified_fields(name, bases, namespace, _fname, _fvalue, extensions) else: # New fieds @@ -705,14 +711,25 @@ class BaseElement(metaclass=BaseMeta): """ class BaseMetaFeature(BaseMeta): - pass + def __call__(cls: Type, *args: Any, **kw: Any): # intentionally untyped + """BaseFeature new instance""" + + if cls._BoundAppliance is None: + raise FeatureNotBound() + + obj = super().__call__(*args, **kw) + + + return obj class BaseFeature(BaseElement,metaclass=BaseMetaFeature): """BaseFeature class Base class for Appliance's Features. Features are optional traits of an appliance. """ + _BoundAppliance:"Optional[Type[BaseAppliance]]" = None Enabled:bool=False + class BaseMetaAppliance(BaseMeta): @@ -761,11 +778,13 @@ class BaseMetaAppliance(BaseMeta): super().save_values(cls,name,bases,namespace,extensions) for _ftname,_ftvalue in extensions["modified_features"].items(): + _ftvalue._BoundAppliance = cls cls.__DABSchema__["features"][_ftname] = _ftvalue for _ftname,_ftvalue in extensions["new_features"].items(): + _ftvalue._BoundAppliance = cls cls.__DABSchema__["features"][_ftname] = _ftvalue - def modify_object(cls:Type, obj,extensions : dict[str,Any]): # intentionally untyped + def modify_object(cls:Type, obj, extensions : dict[str,Any]): # intentionally untyped for _ftname,_ftvalue in cls.__DABSchema__["features"].items(): instft = _ftvalue() object.__setattr__(obj, _ftname,instft ) diff --git a/test/test_model.py b/test/test_model.py index 3ef1c65..4bca551 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -16,6 +16,7 @@ import textwrap from typing import List, Optional, Dict, Union, Tuple, Set, FrozenSet, TypeVar, Generic, Any, Annotated from pprint import pprint from frozendict import frozendict +import math print(__name__) print(__package__) @@ -84,32 +85,32 @@ class MainTests(unittest.TestCase): with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: str = 12 + test: str = 12 with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: int = "value" + test: int = "value" with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: float = "value" + test: float = "value" with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: complex = "value" + test: complex = "value" with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: bool = "value" + test: bool = "value" with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: bytes = "value" + test: bytes = "value" def test_annotation(self): """Testing first appliance level, and Field types (simple annotations)""" @@ -147,38 +148,38 @@ class MainTests(unittest.TestCase): with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "str" = 12 + test: "str" = 12 with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "int" = "value" + test: "int" = "value" with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "float" = "value" + test: "float" = "value" with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "complex" = "value" + test: "complex" = "value" with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "bool" = "value" + test: "bool" = "value" with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "bytes" = "value" + test: "bytes" = "value" # class cannot be created if not annotated field with self.assertRaises(dm.NotAnnotatedField): class _(dm.BaseAppliance): - _ = "default value" + test = "default value" def test_optionnal(self): """Testing first appliance level, and Field types (Optionnal annotations)""" @@ -415,22 +416,22 @@ class MainTests(unittest.TestCase): with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: List[int] = ["a"] + test: List[int] = ["a"] with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "List[int]" = ["a"] + test: "List[int]" = ["a"] with self.assertRaises(dm.IncompletelyAnnotatedField): class _(dm.BaseAppliance): - _: List = [1, 2] + test: List = [1, 2] with self.assertRaises(dm.IncompletelyAnnotatedField): class _(dm.BaseAppliance): - _: "List" = [1, 2] + test: "List" = [1, 2] def test_containers__dict(self): """Testing first appliance level, and Field types (Dict)""" @@ -455,29 +456,29 @@ class MainTests(unittest.TestCase): with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: Dict[int, str] = {1: 64, 2: "b"} + test: Dict[int, str] = {1: 64, 2: "b"} with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "Dict[int, str]" = {1: 64, 2: "b"} + test: "Dict[int, str]" = {1: 64, 2: "b"} with self.assertRaises(dm.IncompletelyAnnotatedField): class _(dm.BaseAppliance): - _: Dict = {1: 64, 2: "b"} + 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.BaseAppliance): - _: Dict[int] = {1: 64, 2: "b"} + 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.BaseAppliance): - _: "Dict[int]" = {1: 64, 2: "b"} + test: "Dict[int]" = {1: 64, 2: "b"} def test_containers__tuple(self): """Testing first appliance level, and Field types (Tuple)""" @@ -509,32 +510,32 @@ class MainTests(unittest.TestCase): with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: Tuple[int] = "a" + test: Tuple[int] = "a" with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "Tuple[int]" = "a" + test: "Tuple[int]" = "a" with self.assertRaises(dm.IncompletelyAnnotatedField): class _(dm.BaseAppliance): - _: Tuple = (1, 2) + test: Tuple = (1, 2) with self.assertRaises(dm.IncompletelyAnnotatedField): class _(dm.BaseAppliance): - _: "Tuple" = (1, 2) + test: "Tuple" = (1, 2) with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "Tuple[int,...]" = (1, "a") + test: "Tuple[int,...]" = (1, "a") with self.assertRaises(dm.InvalidFieldValue): class _(dm.BaseAppliance): - _: "tuple[int,...]" = (1, "a") + test: "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 @@ -701,13 +702,13 @@ class MainTests(unittest.TestCase): with self.assertRaises(dm.InvalidFieldAnnotation): class _(dm.BaseAppliance): - _: Annotated[str, "foo2"] = "default value2" + 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.BaseAppliance): - _: Annotated[str] = "default value2" + test: Annotated[str] = "default value2" def test_immutable_fields_inheritance(self): """Testing first appliance level, and Field types (simple)""" @@ -1177,9 +1178,7 @@ class MainTests(unittest.TestCase): def test_feature(self): """Testing first appliance feature, and Field types (simple)""" - - - + # class can be created class Appliance1(dm.BaseAppliance): VarStrOuter: str = "testvalue APPLIANCE" @@ -1198,8 +1197,6 @@ class MainTests(unittest.TestCase): def test_feature_inheritance(self): """Testing first appliance feature, and Field types (simple)""" - - # class can be created class Appliance1(dm.BaseAppliance): @@ -1322,7 +1319,6 @@ class MainTests(unittest.TestCase): class Appliance4(Appliance3): VarStr = "testvalue moded" - app4 = Appliance4() self.assertEqual(app1.VarStr,"testvalue1") @@ -1333,10 +1329,31 @@ class MainTests(unittest.TestCase): app1b = Appliance1() app2b = Appliance2() app3b = Appliance3() + self.assertEqual(app1b.VarStr,"testvalue1") self.assertEqual(app2b.VarStr,"testvalue1") self.assertEqual(app3b.VarStr,"testvalue1") + + def test_feature_register(self): + """Testing first appliance feature, and Field types (simple)""" + + # class can be created + class Appliance1(dm.BaseAppliance): + pass + class Feature1(dm.BaseFeature): + _BoundAppliance = Appliance1 + VarStrInner: str = "testvalue FEATURE1" + + print(dir(Feature1)) + + def test_feature_register_defect(self): + + class Feature1(dm.BaseFeature): + pass + with self.assertRaises(dm.FeatureNotBound): + feat1 = Feature1() + # ---------- main ---------- if __name__ == "__main__":