continue work

This commit is contained in:
chacha
2024-12-08 01:04:27 +01:00
parent 4c17436cea
commit 0eef35e36f
3 changed files with 55 additions and 111 deletions

View File

@@ -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]

View File

@@ -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

View File

@@ -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))