diff --git a/src/dabmodel/model.py b/src/dabmodel/model.py index d028d20..ae8c75b 100644 --- a/src/dabmodel/model.py +++ b/src/dabmodel/model.py @@ -2,7 +2,7 @@ from __future__ import annotations from abc import ABC, ABCMeta from uuid import uuid4 -from typing import Annotated, ClassVar, Any, Self, TypeVar, TypeAlias, Generic +from typing import Annotated, ClassVar, Any, Self, TypeVar, TypeAlias, Generic, Union from datetime import datetime from copy import deepcopy, copy from typing_extensions import dataclass_transform, get_origin @@ -128,7 +128,7 @@ class BaseElement( IBaseElement, ABC, validate_assignment=True, - revalidate_instances="subclass-instances", + # revalidate_instances="subclass-instances", # pydantic issue #10681 validate_default=True, metaclass=BaseElementMeta, ): @@ -146,6 +146,11 @@ class BaseElement( @model_validator(mode="before") @classmethod def __default_values_override_hook__(cls, values: T_BaseElement_ConfigMethod_Arg) -> T_BaseElement_ConfigMethod_Arg: + import random + + r = random.randint(0, 100) + print(f"{r} __default_values_override_hook__ {cls}") + print(values) # extracting default values that were set in model fields cls._saved_default_value = dict() for field_key, field_val in cls.model_fields.items(): @@ -161,9 +166,17 @@ class BaseElement( method.__func__(cls, cls._saved_default_value) # applying user-defined values - for key, _ in values.items(): - cls._saved_default_value[key] = values[key] + print(f"{r} ??") + print(values) + for key, val in values.items(): + if key == f"{r} features": + print(f"{r} HEYYYYYYYY") + print(key) + print(val) + cls._saved_default_value[key] = val + + print(f"{r} __default_values_override_hook__ {cls} DONE") return cls._saved_default_value @@ -177,20 +190,17 @@ class BaseFeature(BaseElement, ABC): ... T_Feature = TypeVar("T_Feature", bound=BaseFeature) -T_BaseAppliance_Feature_OrderedSet: TypeAlias = dict[type[T_Feature], T_Feature] - -""" -class MySet(set): - def __init__(self, _iter, klass=None): - if klass is not None: - for item in iter: - if not isinstance(item, klass): - raise Exception("Error") - super(MySet, self).__init__(_iter) -""" +# T_BaseAppliance_Feature_OrderedSet: TypeAlias = dict[type[T_Feature], T_Feature] +# T_BaseAppliance_Feature_OrderedSet: TypeAlias = dict[str, T_Feature] -class BaseAppliance(BaseElement, ABC): +def get_discriminator_value(v: Any) -> str: + if isinstance(v, dict): + return v.get("fruit", v.get("filling")) + return getattr(v, "fruit", getattr(v, "filling", None)) + + +class BaseAppliance(Generic[T_Feature], BaseElement, ABC): cpu_cnt: Annotated[StrictInt, DABField(1, gt=0)] ram_size: Annotated[ByteSize, DABField(256, gt=128)] @@ -206,26 +216,32 @@ class BaseAppliance(BaseElement, ABC): dabinst_description: Annotated[StrictStr | None, DABField("")] dabinst_creationdate: Annotated[AwareDatetime | None, DABField(datetime.now(tz=pytz.utc))] - features: Annotated[SerializeAsAny[T_BaseAppliance_Feature_OrderedSet], DABField({})] + features: SerializeAsAny[dict[str, T_Feature]] = DABField({}) - @field_validator("features", mode="after") - @classmethod - def __serialize_features(cls, features: T_BaseAppliance_Feature_OrderedSet) -> dict[str, BaseFeature]: - return {str(key.__name__): value for key, value in features.items()} # or __qualname__ + # @field_validator("features", mode="after") + # @classmethod + # def __serialize_features(cls, features: dict[str, Any]) -> dict[str, Any]: + # print("XXXX") + # print(type(features)) + # print(features) + # return {str(key): value for key, value in features.items()} # or __qualname__ @NoInstanceMethod @classmethod - def add_feature(cls, feat: BaseFeature): + def add_feature(cls, feat: T_Feature): print("Addfeature") + print(feat) print(cls._saved_default_value["features"]) - cls._saved_default_value["features"][type(feat)] = feat + cls._saved_default_value["features"][type(feat).__name__] = feat + print(cls._saved_default_value["features"]) + print("Addfeature DONE") @NoInstanceMethod @classmethod - def del_feature(cls, type_feat: type[BaseFeature]): - del cls._saved_default_value["features"][type_feat] + def del_feature(cls, type_feat: type[T_Feature]): + del cls._saved_default_value["features"][type_feat.__name__] @NoInstanceMethod @classmethod def get_feature(cls, type_feat: type[T_Feature]) -> T_Feature: - return cls._saved_default_value["features"][type_feat] + return cls._saved_default_value["features"][type_feat.__name__] diff --git a/test/test_debug.py b/test/test_debug.py new file mode 100644 index 0000000..52d40c7 --- /dev/null +++ b/test/test_debug.py @@ -0,0 +1,37 @@ +from pydantic import BaseModel, SerializeAsAny + + +class commonbase( + BaseModel, + revalidate_instances="subclass-instances", # toogle to generate error +): ... + + +class basechild(commonbase): + test_val: int = 1 + + +class derivedchild(basechild): + test_val2: int = 2 + + +class container(commonbase): + + ct_child_1: dict[str, basechild] = {} + ct_child_2: SerializeAsAny[dict[str, basechild]] = {} + ct_child_3: dict[str, SerializeAsAny[basechild]] = {} + + +if __name__ == "__main__": + test_val = container( + ct_child_1={"test1": derivedchild()}, + ct_child_2={"test2": derivedchild()}, + ct_child_3={"test3": derivedchild()}, + ) + + print(test_val.model_dump_json(indent=1)) + + print(test_val.model_dump()) + assert "test_val2" not in test_val.model_dump()["ct_child_1"]["test1"] + assert "test_val2" in test_val.model_dump()["ct_child_2"]["test2"] + assert "test_val2" in test_val.model_dump()["ct_child_3"]["test3"] diff --git a/test/test_model.py b/test/test_model.py index 8a800c8..5e76d77 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -76,15 +76,17 @@ class MyAppliance2(MyAppliance): class MyAppliance3(dabmodel.BaseAppliance): - class MyFeature(MyAppliance.MyFeature): + class MyFeature6(MyAppliance.MyFeature): # testtt: Annotated[MyAppliance.MyFeature, Field(MyAppliance.MyFeature())] # error case + test_integer: Annotated[int, dabmodel.DABField(200, ge=0)] @dabmodel.default_values_override @classmethod def __override_config__(cls, values): print("!!! CONFIG Feature 1 (modified)") - values["template_id"] = "421d61cb-e364-46d8-9b77-ec439f1fa777" + values["template_id"] = "421d61cb-e364-46d8-9b77-ec439f1fa778" values["template_short_name"] = "my-feature-1-bis" + values["test_integer"] = 666 # testtt: Annotated[MyAppliance.MyFeature, Field(MyAppliance.MyFeature())] # error case @@ -97,7 +99,11 @@ class MyAppliance3(dabmodel.BaseAppliance): values["template_long_name"] = "My appliance template 3 !!" values["template_description"] = """A very nice Appliance 3""" values["ram_size"] = 3076 - cls.add_feature(cls.MyFeature()) + print("CREATE FEATURE") + f = cls.MyFeature6() + # print(f) + cls.add_feature(f) + print("!!! CONFIG Appliance 3 DONE") class TestModel(unittest.TestCase): @@ -126,9 +132,16 @@ class TestModel(unittest.TestCase): print(app3.model_dump_json(indent=1)) print("aaa") + app3 = MyAppliance3(dabinst_short_name="my-app-3", template_description="FORCED") + print("aaa1") tmp_json = app3.model_dump_json(indent=1) + print("bbb") print(tmp_json) - recreated_obj = MyAppliance3.parse_raw(tmp_json) + print("ccc") + recreated_obj = MyAppliance3.model_validate_json(tmp_json) + print("ddd") print(recreated_obj) + print("eee") + print(recreated_obj.model_dump_json(indent=1)) # app3.add_feature(MyAppliance.MyFeature()) # error case (add_feature not callable from instance)