continue work
This commit is contained in:
@@ -34,7 +34,9 @@ classifiers = [
|
||||
]
|
||||
dependencies = [
|
||||
'importlib-metadata; python_version<"3.9"',
|
||||
'packaging'
|
||||
'packaging',
|
||||
'pydantic',
|
||||
'runtype'
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
@@ -78,7 +80,7 @@ test = ["chacha_cicd_helper"]
|
||||
coverage-check = ["chacha_cicd_helper"]
|
||||
complexity-check = ["chacha_cicd_helper"]
|
||||
quality-check = ["chacha_cicd_helper"]
|
||||
type-check = ["chacha_cicd_helper"]
|
||||
type-check = ["chacha_cicd_helper","types-pytz"]
|
||||
doc-gen = ["chacha_cicd_helper"]
|
||||
|
||||
# [project.scripts]
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# pylint: disable=C0114,C0115,C0116
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, ABCMeta, abstractmethod
|
||||
from abc import ABC, ABCMeta
|
||||
from uuid import uuid4
|
||||
from typing import Annotated, ClassVar, Any, Self, TypeVar, TypeAlias, Generic, Union
|
||||
from typing import Annotated, ClassVar, Any, Self, TypeVar, TypeAlias, Generic
|
||||
from datetime import datetime
|
||||
from copy import deepcopy, copy
|
||||
from typing_extensions import dataclass_transform, get_origin
|
||||
from typing_extensions import dataclass_transform
|
||||
from pydantic import (
|
||||
ConfigDict,
|
||||
BaseModel,
|
||||
StrictInt,
|
||||
StrictStr,
|
||||
@@ -16,16 +16,12 @@ from pydantic import (
|
||||
AwareDatetime,
|
||||
UUID4,
|
||||
model_validator,
|
||||
field_validator,
|
||||
field_serializer,
|
||||
SerializeAsAny,
|
||||
)
|
||||
from pydantic.fields import Field, _Unset, PydanticUndefined
|
||||
from pydantic._internal._model_construction import ModelMetaclass, PydanticModelField
|
||||
from pydantic._internal._generics import PydanticGenericMetadata
|
||||
from pydantic._internal._decorators import ensure_classmethod_based_on_signature
|
||||
import pytz
|
||||
import inspect
|
||||
|
||||
from runtype import issubclass as runtype_issubclass
|
||||
|
||||
@@ -49,6 +45,8 @@ class NoInstanceMethod:
|
||||
|
||||
|
||||
def DABField(default: Any = PydanticUndefined, *, final: bool | None = _Unset, finalize_only: bool | None = _Unset, **kwargs):
|
||||
kwargs["final"] = final is not None
|
||||
kwargs["finalize_only"] = finalize_only is not None
|
||||
return Field(default, **kwargs)
|
||||
|
||||
|
||||
@@ -92,12 +90,11 @@ class BaseElementMeta(ModelMetaclass, ABCMeta):
|
||||
cls_name,
|
||||
bases,
|
||||
namespace,
|
||||
__pydantic_generic_metadata__,
|
||||
__pydantic_reset_parent_namespace__,
|
||||
_create_model_module,
|
||||
None, #__pydantic_generic_metadata__,
|
||||
True, #__pydantic_reset_parent_namespace__,
|
||||
None, #_create_model_module,
|
||||
**kwargs,
|
||||
)
|
||||
print(cls_name)
|
||||
|
||||
assert issubclass(result, IBaseElement), "Only IBaseElement subclasses are supported"
|
||||
|
||||
@@ -148,18 +145,12 @@ class BaseElement(
|
||||
@classmethod
|
||||
def __default_values_override_hook__(cls, values: T_BaseElement_ConfigMethod_Arg) -> T_BaseElement_ConfigMethod_Arg:
|
||||
# extracting default values that were set in model fields
|
||||
cls._saved_default_value = dict()
|
||||
cls._saved_default_value = {}
|
||||
for field_key, field_val in cls.model_fields.items():
|
||||
assert field_val.annotation is not None, "all fields must have annotation"
|
||||
assert not runtype_issubclass(
|
||||
field_val.annotation, BaseFeature
|
||||
), "Features can only be in Appliance's features[] dict attribute"
|
||||
"""
|
||||
if field_key == "features":
|
||||
cls._saved_default_value[field_key] = dict()
|
||||
for feat_key, feat_value in field_val.items():
|
||||
cls._saved_default_value[field_key][feat_key] = feat_value.dict()
|
||||
"""
|
||||
if field_val.default != PydanticUndefined:
|
||||
cls._saved_default_value[field_key] = deepcopy(field_val.default)
|
||||
for method, _ in cls._config_element.default_values_override_methods.items():
|
||||
@@ -169,46 +160,24 @@ class BaseElement(
|
||||
|
||||
return cls._saved_default_value
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def _default_values_override_hook__input_apply__(
|
||||
cls,
|
||||
values: T_BaseElement_ConfigMethod_Arg,
|
||||
): ...
|
||||
|
||||
|
||||
class BaseFeature(BaseElement, ABC):
|
||||
@NoInstanceMethod
|
||||
@classmethod
|
||||
def _default_values_override_hook__input_apply__(
|
||||
cls,
|
||||
values: T_BaseElement_ConfigMethod_Arg,
|
||||
):
|
||||
print(f"BaseFeature._default_values_override_hook__input_apply__ {cls}")
|
||||
# applying user-defined values
|
||||
for attr_key, attr_val in values.items():
|
||||
assert attr_key in cls.model_fields, f"given feature attribute does not exist ({attr_key})"
|
||||
cls._saved_default_value[attr_key] = attr_val
|
||||
print(f"BaseFeature._default_values_override_hook__input_apply__ {cls} DONE")
|
||||
|
||||
|
||||
def default_values_override(func: T_BaseElement_ConfigMethod) -> T_BaseElement_ConfigMethod:
|
||||
func = ensure_classmethod_based_on_signature(func)
|
||||
setattr(func, "default_values_override", lambda: True)
|
||||
return func
|
||||
|
||||
class BaseFeature(BaseElement, ABC):
|
||||
...
|
||||
|
||||
T_Feature = TypeVar("T_Feature", bound=BaseFeature)
|
||||
|
||||
|
||||
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)]
|
||||
swap_size: Annotated[ByteSize, DABField(200, ge=0)]
|
||||
@@ -223,45 +192,7 @@ class BaseAppliance(Generic[T_Feature], BaseElement, ABC):
|
||||
dabinst_description: Annotated[StrictStr | None, DABField("")]
|
||||
dabinst_creationdate: Annotated[AwareDatetime | None, DABField(datetime.now(tz=pytz.utc))]
|
||||
|
||||
features: SerializeAsAny[dict[str, T_Feature]] = DABField({})
|
||||
|
||||
@NoInstanceMethod
|
||||
@classmethod
|
||||
def add_feature(cls, feat: T_Feature):
|
||||
cls._saved_default_value["features"][type(feat).__name__] = feat.dict()
|
||||
|
||||
@NoInstanceMethod
|
||||
@classmethod
|
||||
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.__name__]
|
||||
|
||||
@NoInstanceMethod
|
||||
@classmethod
|
||||
def _default_values_override_hook__input_apply__(
|
||||
cls,
|
||||
values: T_BaseElement_ConfigMethod_Arg,
|
||||
):
|
||||
print(f"BaseAppliance._default_values_override_hook__input_apply__ {cls}")
|
||||
# applying user-defined values
|
||||
for attr_key, attr_val in values.items():
|
||||
if attr_key == "features":
|
||||
if cls._saved_default_value["features"] is None:
|
||||
cls._saved_default_value["features"] = {}
|
||||
for feature_key, feature_val in attr_val.items():
|
||||
print(f"searching feature: {feature_key}")
|
||||
assert hasattr(cls, feature_key), f"feature not found ({feature_key})"
|
||||
cls_feature = getattr(cls, feature_key)
|
||||
assert (
|
||||
cls_feature is not None and inspect.isclass(cls_feature) and issubclass(cls_feature, BaseFeature),
|
||||
"The requested feature does not exist in the current Appliance class tree",
|
||||
)
|
||||
cls._saved_default_value["features"][feature_key] = cls_feature(**feature_val)
|
||||
else:
|
||||
assert attr_key in cls.model_fields, f"given attribute does not exist ({attr_key})"
|
||||
cls._saved_default_value[attr_key] = attr_val
|
||||
print(f"BaseAppliance._default_values_override_hook__input_apply__ {cls} DONE")
|
||||
def default_values_override(func: T_BaseElement_ConfigMethod) -> T_BaseElement_ConfigMethod:
|
||||
func = ensure_classmethod_based_on_signature(func)
|
||||
setattr(func, "default_values_override", lambda: True)
|
||||
return func
|
||||
|
||||
@@ -17,7 +17,7 @@ print(__name__)
|
||||
print(__package__)
|
||||
|
||||
from src import dabmodel
|
||||
from typing import Annotated, Any
|
||||
from typing import Annotated, Any, Optional
|
||||
from uuid import uuid4
|
||||
import json
|
||||
from uuid import UUID
|
||||
@@ -40,7 +40,7 @@ class UUIDEncoder(json.JSONEncoder):
|
||||
class MyAppliance(dabmodel.BaseAppliance):
|
||||
app_specifi_integer_arg: Annotated[StrictInt | None, dabmodel.DABField(42)]
|
||||
|
||||
class MyFeature(dabmodel.BaseFeature):
|
||||
class _MyFeature(dabmodel.BaseFeature):
|
||||
@dabmodel.default_values_override
|
||||
@classmethod
|
||||
def __override_config__(cls, values):
|
||||
@@ -49,8 +49,9 @@ class MyAppliance(dabmodel.BaseAppliance):
|
||||
values["template_short_name"] = "my-feature-1"
|
||||
values["template_long_name"] = "My feature template 1 !!"
|
||||
values["template_description"] = """A very nice FEature 1"""
|
||||
MyFeature : Annotated[Optional[_MyFeature],dabmodel.DABField(_MyFeature())]
|
||||
|
||||
class MyFeature2(dabmodel.BaseFeature):
|
||||
class _MyFeature2(dabmodel.BaseFeature):
|
||||
@dabmodel.default_values_override
|
||||
@classmethod
|
||||
def __override_config__(cls, values):
|
||||
@@ -59,6 +60,7 @@ class MyAppliance(dabmodel.BaseAppliance):
|
||||
values["template_short_name"] = "my-feature-2"
|
||||
values["template_long_name"] = "My feature template 2 !!"
|
||||
values["template_description"] = """A very nice FEature 2"""
|
||||
MyFeature2 : Annotated[Optional[_MyFeature2],dabmodel.DABField(_MyFeature2())]
|
||||
|
||||
@dabmodel.default_values_override
|
||||
@classmethod
|
||||
@@ -70,8 +72,8 @@ class MyAppliance(dabmodel.BaseAppliance):
|
||||
values["template_long_name"] = "My appliance template 1 !!"
|
||||
values["template_description"] = """A very nice Appliance 1"""
|
||||
values["ram_size"] = 1024
|
||||
cls.add_feature(cls.MyFeature())
|
||||
cls.add_feature(cls.MyFeature2())
|
||||
#cls.add_feature(cls.MyFeature())
|
||||
#cls.add_feature(cls.MyFeature2())
|
||||
|
||||
|
||||
class MyAppliance2(MyAppliance):
|
||||
@@ -84,16 +86,21 @@ class MyAppliance2(MyAppliance):
|
||||
values["template_short_name"] = "my-app- tem 2"
|
||||
values["template_long_name"] = "My appliance template 2 !!"
|
||||
values["template_description"] = """A very nice Appliance 2"""
|
||||
cls.del_feature(MyAppliance.MyFeature)
|
||||
values["MyFeature"] = None
|
||||
#cls.del_feature(MyAppliance.MyFeature)
|
||||
# values["features"]["MyFeature2"].template_description = """Override feature desc"""
|
||||
|
||||
|
||||
class MyAppliance3(dabmodel.BaseAppliance):
|
||||
|
||||
class MyFeature6(MyAppliance.MyFeature):
|
||||
|
||||
class _MyFeature6(MyAppliance._MyFeature):
|
||||
# testtt: Annotated[MyAppliance.MyFeature, Field(MyAppliance.MyFeature())] # error case
|
||||
|
||||
test_integer: Annotated[int, dabmodel.DABField(200, ge=0)]
|
||||
|
||||
test_integer1234: Annotated[int, dabmodel.DABField(200, ge=0)]
|
||||
#test_integer: int = dabmodel.DABField(200, ge=0)
|
||||
|
||||
|
||||
@dabmodel.default_values_override
|
||||
@classmethod
|
||||
def __override_config__(cls, values):
|
||||
@@ -101,8 +108,10 @@ class MyAppliance3(dabmodel.BaseAppliance):
|
||||
values["template_id"] = "421d61cb-e364-46d8-9b77-ec439f1fa778"
|
||||
values["template_short_name"] = "my-feature-1-bis"
|
||||
values["test_integer"] = 666
|
||||
|
||||
class MyFeature7(dabmodel.BaseFeature):
|
||||
|
||||
MyFeature6 : Annotated[Optional[_MyFeature6],dabmodel.DABField(_MyFeature6())]
|
||||
|
||||
class _MyFeature7(dabmodel.BaseFeature):
|
||||
# testtt: Annotated[MyAppliance.MyFeature, Field(MyAppliance.MyFeature())] # error case
|
||||
test_integer_2: Annotated[int, dabmodel.DABField(759, ge=0)]
|
||||
|
||||
@@ -115,6 +124,7 @@ class MyAppliance3(dabmodel.BaseAppliance):
|
||||
values["template_long_name"] = "My appliance template 7 !!"
|
||||
values["template_description"] = """A very nice Appliance 7"""
|
||||
values["test_integer_2"] = 3844
|
||||
MyFeature7 : Annotated[Optional[_MyFeature7],dabmodel.DABField(_MyFeature7())]
|
||||
|
||||
# testtt: Annotated[MyAppliance.MyFeature, Field(MyAppliance.MyFeature())] # error case
|
||||
|
||||
@@ -128,14 +138,14 @@ class MyAppliance3(dabmodel.BaseAppliance):
|
||||
values["template_description"] = """A very nice Appliance 3"""
|
||||
values["ram_size"] = 3076
|
||||
print("CREATE FEATURE")
|
||||
cls.add_feature(cls.MyFeature6())
|
||||
cls.add_feature(cls.MyFeature7())
|
||||
#cls.add_feature(cls.MyFeature6())
|
||||
#cls.add_feature(cls.MyFeature7())
|
||||
print("!!! CONFIG Appliance 3 DONE")
|
||||
|
||||
|
||||
class MyAppliance4(MyAppliance):
|
||||
|
||||
class MyFeature8(dabmodel.BaseFeature):
|
||||
class _MyFeature8(dabmodel.BaseFeature):
|
||||
# testtt: Annotated[MyAppliance.MyFeature, Field(MyAppliance.MyFeature())] # error case (nested feature)
|
||||
# test_integer_10: Annotated[int, dabmodel.DABField(3189, ge=0, toto="tata")] # error case (extra field)
|
||||
test_integer_10: Annotated[int, dabmodel.DABField(3189, ge=0, toto="tata")]
|
||||
@@ -150,6 +160,7 @@ class MyAppliance4(MyAppliance):
|
||||
values["template_description"] = """A very nice Appliance 8"""
|
||||
values["test_integer_10"] = 951753
|
||||
# values["tete"] = 1 # error case (extra field in feature)
|
||||
MyFeature8 : Annotated[Optional[_MyFeature8],dabmodel.DABField(_MyFeature8())]
|
||||
|
||||
# testtt: Annotated[MyAppliance.MyFeature, Field(MyAppliance.MyFeature())] # error case (feature not in features[] list)
|
||||
|
||||
@@ -163,7 +174,7 @@ class MyAppliance4(MyAppliance):
|
||||
values["template_description"] = """A very nice Appliance 4"""
|
||||
values["ram_size"] = 954
|
||||
print("CREATE FEATURE")
|
||||
cls.add_feature(cls.MyFeature8())
|
||||
#cls.add_feature(cls.MyFeature8())
|
||||
print("!!! CONFIG Appliance 4 DONE")
|
||||
|
||||
|
||||
@@ -176,11 +187,11 @@ class TestModel(unittest.TestCase):
|
||||
|
||||
def test_model(self):
|
||||
|
||||
feature1 = MyAppliance.MyFeature()
|
||||
feature1 = MyAppliance._MyFeature()
|
||||
print(feature1)
|
||||
print(MyAppliance.MyFeature)
|
||||
print(MyAppliance.MyFeature.__name__)
|
||||
print(MyAppliance.MyFeature.__class__)
|
||||
print(MyAppliance._MyFeature)
|
||||
print(MyAppliance._MyFeature.__name__)
|
||||
print(MyAppliance._MyFeature.__class__)
|
||||
print("==")
|
||||
print(feature1.model_dump_json(indent=1))
|
||||
|
||||
@@ -194,7 +205,7 @@ class TestModel(unittest.TestCase):
|
||||
|
||||
app3 = MyAppliance3(dabinst_short_name="my-app-3", template_description="FORCED")
|
||||
tmp_json = app3.dict()
|
||||
tmp_json["features"]["MyFeature7"]["test_integer_2"] = 123
|
||||
tmp_json["MyFeature7"]["test_integer_2"] = 123
|
||||
print(tmp_json)
|
||||
recreated_obj = MyAppliance3.model_validate_json(json.dumps(tmp_json, cls=UUIDEncoder))
|
||||
print(recreated_obj)
|
||||
@@ -202,16 +213,16 @@ class TestModel(unittest.TestCase):
|
||||
|
||||
app4 = MyAppliance4(dabinst_short_name="my-app-4", template_description="FORCED2")
|
||||
tmp_json = app4.dict()
|
||||
tmp_json["features"]["MyFeature"]["template_description"] = "blablabla"
|
||||
tmp_json["features"]["MyFeature2"]["template_description"] = "blablabla2"
|
||||
tmp_json["MyFeature"]["template_description"] = "blablabla"
|
||||
tmp_json["MyFeature2"]["template_description"] = "blablabla2"
|
||||
print(tmp_json)
|
||||
recreated_obj = MyAppliance4.model_validate_json(json.dumps(tmp_json, cls=UUIDEncoder))
|
||||
print(recreated_obj)
|
||||
print(recreated_obj.model_dump_json(indent=1))
|
||||
|
||||
# tmp_json["non-existing"] = "test" # error case
|
||||
# tmp_json["features"]["non-existing"] = "test" # error case
|
||||
# tmp_json["features"]["MyFeature"]["132"] = "test" # error case
|
||||
# tmp_json["non-existing"] = "test" # error case
|
||||
# tmp_json["MyFeature"]["132"] = "test" # error case
|
||||
|
||||
recreated_obj = MyAppliance4.model_validate_json(json.dumps(tmp_json, cls=UUIDEncoder))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user