first comit

This commit is contained in:
cclecle
2024-10-20 12:25:41 +01:00
parent 19b88fc7c3
commit 8ab6c8e179
6 changed files with 229 additions and 80 deletions

View File

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

View File

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

View File

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

View File

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