first comit
This commit is contained in:
2
.project
2
.project
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<projectDescription>
|
<projectDescription>
|
||||||
<name>{{project_name}}</name>
|
<name>dabmodel</name>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
<projects>
|
<projects>
|
||||||
</projects>
|
</projects>
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ Main module __init__ file.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .__metadata__ import __version__, __Summuary__, __Name__
|
from .__metadata__ import __version__, __Summuary__, __Name__
|
||||||
from .test_module import test_function
|
from .model import DABField, BaseFeature, BaseAppliance, default_values_override
|
||||||
|
|||||||
141
src/dabmodel/model.py
Normal file
141
src/dabmodel/model.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import ABC, ABCMeta
|
||||||
|
from uuid import uuid4
|
||||||
|
from typing import Annotated, ClassVar, Any, Callable, Self, TypeVar, TypeAlias
|
||||||
|
from typing_extensions import dataclass_transform
|
||||||
|
from pydantic import BaseModel, StrictInt, StrictStr, constr, ByteSize, AwareDatetime, UUID4, model_validator
|
||||||
|
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
|
||||||
|
from datetime import datetime
|
||||||
|
import pytz
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
|
||||||
|
def DABField(default: Any = PydanticUndefined, *, final: bool | None = _Unset, finalize_only: bool | None = _Unset, **kwargs):
|
||||||
|
kwargs["frozen"] = True
|
||||||
|
return Field(default, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
T_BaseElement = TypeVar("T_BaseElement", bound="BaseElement")
|
||||||
|
T_BaseElement_ConfigMethod_Arg: TypeAlias = dict[str, Any]
|
||||||
|
T_BaseElement_ConfigMethod: TypeAlias = "classmethod[T_BaseElement, [T_BaseElement_ConfigMethod_Arg], T_BaseElement_ConfigMethod_Arg]"
|
||||||
|
T_BaseElement_ConfigMethod_OrderedSet: TypeAlias = dict[T_BaseElement_ConfigMethod, None]
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigElement:
|
||||||
|
default_values_override_methods: T_BaseElement_ConfigMethod_OrderedSet = {}
|
||||||
|
main_build_method: T_BaseElement_ConfigMethod_OrderedSet = {}
|
||||||
|
|
||||||
|
|
||||||
|
class IBaseElement(ABC):
|
||||||
|
_config_element: ConfigElement = ConfigElement()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass_transform(kw_only_default=True, field_specifiers=(PydanticModelField,))
|
||||||
|
class BaseElementMeta(ModelMetaclass, ABCMeta):
|
||||||
|
def __new__(
|
||||||
|
mcs,
|
||||||
|
cls_name: str,
|
||||||
|
bases: tuple[type[Any], ...],
|
||||||
|
namespace: dict[str, Any],
|
||||||
|
__pydantic_generic_metadata__: PydanticGenericMetadata | None = None,
|
||||||
|
__pydantic_reset_parent_namespace__: bool = True,
|
||||||
|
_create_model_module: str | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> type:
|
||||||
|
result = super().__new__(
|
||||||
|
mcs,
|
||||||
|
cls_name,
|
||||||
|
bases,
|
||||||
|
namespace,
|
||||||
|
__pydantic_generic_metadata__,
|
||||||
|
__pydantic_reset_parent_namespace__,
|
||||||
|
_create_model_module,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
# print(result)
|
||||||
|
# print(type(result))
|
||||||
|
assert issubclass(result, IBaseElement)
|
||||||
|
# copy base class default-configs
|
||||||
|
if not "_config_element" in result.__dict__:
|
||||||
|
# print(result.__base__)
|
||||||
|
# print(type(result.__base__))
|
||||||
|
assert result.__base__ is not None
|
||||||
|
if issubclass(result.__base__, IBaseElement):
|
||||||
|
result._config_element = deepcopy(result.__base__._config_element)
|
||||||
|
# searching and storing current class default-configs
|
||||||
|
for _, method in result.__dict__.items():
|
||||||
|
if isinstance(method, classmethod):
|
||||||
|
if hasattr(method, "default_values_override"):
|
||||||
|
result._config_element.default_values_override_methods[method] = None
|
||||||
|
# todo: find a way to 'lock' and add restriction to a field after inheritance
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class BaseElement(
|
||||||
|
IBaseElement,
|
||||||
|
BaseModel,
|
||||||
|
ABC,
|
||||||
|
validate_assignment=True,
|
||||||
|
revalidate_instances="subclass-instances",
|
||||||
|
validate_default=True,
|
||||||
|
metaclass=BaseElementMeta,
|
||||||
|
):
|
||||||
|
template_id: Annotated[UUID4, DABField(..., repr=True)]
|
||||||
|
template_short_name: Annotated[
|
||||||
|
StrictStr, constr(strip_whitespace=True, to_lower=True, strict=True, max_length=16), DABField(..., repr=True)
|
||||||
|
]
|
||||||
|
template_long_name: Annotated[StrictStr | None, DABField()]
|
||||||
|
template_description: Annotated[StrictStr | None, DABField()]
|
||||||
|
|
||||||
|
@model_validator(mode="before")
|
||||||
|
@classmethod
|
||||||
|
def __default_values_override_hook__(cls, values: T_BaseElement_ConfigMethod_Arg) -> T_BaseElement_ConfigMethod_Arg:
|
||||||
|
new_values = deepcopy(values)
|
||||||
|
for method, _ in cls._config_element.default_values_override_methods.items():
|
||||||
|
new_values = method.__func__(cls, new_values)
|
||||||
|
for key, _ in values.items():
|
||||||
|
new_values[key] = values[key]
|
||||||
|
return new_values
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
compatible_appliance: Annotated[BaseAppliance, DABField()]
|
||||||
|
|
||||||
|
|
||||||
|
# FeatureSet = dict[BaseFeature, None]
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAppliance(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)]
|
||||||
|
|
||||||
|
rootfs_size: Annotated[ByteSize, DABField(2048, ge=2048)]
|
||||||
|
|
||||||
|
dabinst_id: Annotated[UUID4, DABField(uuid4(), repr=True)]
|
||||||
|
dabinst_short_name: Annotated[
|
||||||
|
StrictStr, constr(strip_whitespace=True, to_lower=True, strict=True, max_length=16), DABField(..., repr=True)
|
||||||
|
]
|
||||||
|
dabinst_long_name: Annotated[StrictStr | None, DABField("")]
|
||||||
|
dabinst_description: Annotated[StrictStr | None, DABField("")]
|
||||||
|
dabinst_creationdate: Annotated[AwareDatetime | None, DABField(datetime.now(tz=pytz.utc))]
|
||||||
|
|
||||||
|
# __features: ClassVar[FeatureSet] = []
|
||||||
|
|
||||||
|
def _add_feature(self, feature: BaseFeature) -> None: ...
|
||||||
|
|
||||||
|
# self.__features.append(feature)
|
||||||
|
|
||||||
|
|
||||||
|
# def __init__(self, *args, **data):
|
||||||
|
# super().__init__(*args, **data)
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# 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/>.
|
|
||||||
|
|
||||||
"""Phasellus tellus lectus, volutpat eu dapibus ut, suscipit vel augue.
|
|
||||||
|
|
||||||
Tips:
|
|
||||||
Aliquam non leo vel libero sagittis viverra. Quisque lobortis nunc sit amet augue euismod laoreet.
|
|
||||||
Note:
|
|
||||||
Maecenas volutpat porttitor pretium. Aliquam suscipit quis nisi non imperdiet.
|
|
||||||
Note:
|
|
||||||
Vivamus et efficitur lorem, eget imperdiet tortor. Integer vel interdum sem.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # Only imports the below statements during type checking
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_function(testvar: int) -> int:
|
|
||||||
""" A test function that return testvar+1 and print "Hello world !"
|
|
||||||
|
|
||||||
Proin eget sapien eget ipsum efficitur mollis nec ac nibh.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
Morbi id lectus maximus, condimentum nunc eget, porta felis. In tristique velit tortor.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
testvar: any integer
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
testvar+1
|
|
||||||
"""
|
|
||||||
print("Hello world !")
|
|
||||||
return testvar+1
|
|
||||||
86
test/test_model.py
Normal file
86
test/test_model.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# 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
|
||||||
|
from os import chdir
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pydantic import StrictInt, model_validator
|
||||||
|
|
||||||
|
print(__name__)
|
||||||
|
print(__package__)
|
||||||
|
|
||||||
|
from src import dabmodel
|
||||||
|
from typing import Annotated, Any
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
testdir_path = Path(__file__).parent.resolve()
|
||||||
|
chdir(testdir_path.parent.resolve())
|
||||||
|
|
||||||
|
|
||||||
|
class MyAppliance(dabmodel.BaseAppliance):
|
||||||
|
|
||||||
|
app_specifi_integer_arg: Annotated[StrictInt | None, dabmodel.DABField(42)]
|
||||||
|
|
||||||
|
@dabmodel.default_values_override
|
||||||
|
@classmethod
|
||||||
|
def __override_config__(cls, values):
|
||||||
|
print("!!! CONFIG 1")
|
||||||
|
values["template_id"] = "421d61cb-e364-46d8-9b64-ec439f1faae8"
|
||||||
|
values["template_short_name"] = "my-app- tem 1"
|
||||||
|
values["template_long_name"] = "My appliance template 1 !!"
|
||||||
|
values["template_description"] = """A very nice Appliance 1"""
|
||||||
|
values["ram_size"] = 1024
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
class MyAppliance2(MyAppliance):
|
||||||
|
@dabmodel.default_values_override
|
||||||
|
@classmethod
|
||||||
|
def __override_config__(cls, values):
|
||||||
|
print("!!! CONFIG 2")
|
||||||
|
values["template_id"] = "421d61cb-e664-46d8-9b64-ec439f1fafff"
|
||||||
|
values["template_short_name"] = "my-app- tem 2"
|
||||||
|
values["template_long_name"] = "My appliance template 2 !!"
|
||||||
|
values["template_description"] = """A very nice Appliance 2"""
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
class MyAppliance3(dabmodel.BaseAppliance):
|
||||||
|
@dabmodel.default_values_override
|
||||||
|
@classmethod
|
||||||
|
def __override_config__(cls, values):
|
||||||
|
print("!!! CONFIG 3")
|
||||||
|
values["template_id"] = "421d61cb-e364-46d8-9b64-ec439f1faaaa"
|
||||||
|
values["template_short_name"] = "my-app- tem 3"
|
||||||
|
values["template_long_name"] = "My appliance template 3 !!"
|
||||||
|
values["template_description"] = """A very nice Appliance 3"""
|
||||||
|
values["ram_size"] = 3076
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
class TestModel(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
chdir(testdir_path.parent.resolve())
|
||||||
|
|
||||||
|
def test_version(self):
|
||||||
|
self.assertNotEqual(dabmodel.__version__, "?.?.?")
|
||||||
|
|
||||||
|
def test_model(self):
|
||||||
|
|
||||||
|
app = MyAppliance(dabinst_short_name="my-app-1", app_specifi_integer_arg=123)
|
||||||
|
# app.template_short_name = "tete !"
|
||||||
|
print(app.model_dump_json(indent=1))
|
||||||
|
|
||||||
|
app2 = MyAppliance2(dabinst_short_name="my-app-2", app_specifi_integer_arg=654)
|
||||||
|
# app.template_short_name = "tete !"
|
||||||
|
print(app2.model_dump_json(indent=1))
|
||||||
|
|
||||||
|
app3 = MyAppliance3(dabinst_short_name="my-app-3", app_specifi_integer_arg=654, template_description="FORCED")
|
||||||
|
# app.template_short_name = "tete !"
|
||||||
|
print(app3.model_dump_json(indent=1))
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# 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
|
|
||||||
from os import chdir
|
|
||||||
from io import StringIO
|
|
||||||
from contextlib import redirect_stdout,redirect_stderr
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
print(__name__)
|
|
||||||
print(__package__)
|
|
||||||
|
|
||||||
from src import dabmodel
|
|
||||||
|
|
||||||
testdir_path = Path(__file__).parent.resolve()
|
|
||||||
chdir(testdir_path.parent.resolve())
|
|
||||||
|
|
||||||
class Testtest_module(unittest.TestCase):
|
|
||||||
def setUp(self) -> None:
|
|
||||||
chdir(testdir_path.parent.resolve())
|
|
||||||
|
|
||||||
def test_version(self):
|
|
||||||
self.assertNotEqual(dabmodel.__version__,"?.?.?")
|
|
||||||
|
|
||||||
def test_test_module(self):
|
|
||||||
|
|
||||||
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
|
|
||||||
self.assertEqual(dabmodel.test_function(41),42)
|
|
||||||
self.assertEqual(len(capted_stderr.getvalue()),0)
|
|
||||||
self.assertEqual(capted_stdout.getvalue().strip(),"Hello world !")
|
|
||||||
Reference in New Issue
Block a user