first comit
This commit is contained in:
2
.project
2
.project
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>{{project_name}}</name>
|
||||
<name>dabmodel</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
|
||||
@@ -11,4 +11,4 @@ Main module __init__ file.
|
||||
"""
|
||||
|
||||
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