Compare commits
11 Commits
0.0.1.post
...
0.0.1.post
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f5dade949 | ||
|
|
cce260bc5e | ||
|
|
915a4332ee | ||
|
|
4dca3eb9d1 | ||
|
|
e11c541139 | ||
|
|
637b50b325 | ||
|
|
f45c9cc8f3 | ||
|
|
95b0c298ce | ||
|
|
04a4cf7b36 | ||
|
|
f42a839cff | ||
|
|
7f3a4ef545 |
@@ -35,8 +35,8 @@ classifiers = [
|
||||
dependencies = [
|
||||
'importlib-metadata; python_version<"3.9"',
|
||||
'packaging',
|
||||
'pydantic',
|
||||
'runtype'
|
||||
'frozendict',
|
||||
'typeguard'
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Main module __init__ file.
|
||||
|
||||
from .__metadata__ import __version__, __Summuary__, __Name__
|
||||
from .model import (
|
||||
DABFieldInfo,
|
||||
BaseAppliance,
|
||||
BaseFeature,
|
||||
DABModelException,
|
||||
@@ -24,4 +25,7 @@ from .model import (
|
||||
InvalidFieldValue,
|
||||
InvalidFieldAnnotation,
|
||||
IncompletelyAnnotatedField,
|
||||
ImportForbidden,
|
||||
FunctionForbidden,
|
||||
FrozenDABField
|
||||
)
|
||||
|
||||
@@ -1,9 +1,33 @@
|
||||
from typing import Optional, TypeVar, Generic, Union, get_origin, get_args, List, Dict, Any, Tuple, Set, Annotated, FrozenSet
|
||||
from types import UnionType
|
||||
from frozendict import deepfreeze
|
||||
from copy import deepcopy, copy
|
||||
from pprint import pprint
|
||||
""" dabmodel model module
|
||||
This module implements DAB model classes.
|
||||
This module contains metaclass and bases classes used to create models.
|
||||
BaseAppliance can be used to create a new Appliance Data.
|
||||
BaseFeature can be used to create new Appliance's Features."""
|
||||
|
||||
from typing import (
|
||||
Optional,
|
||||
TypeVar,
|
||||
Generic,
|
||||
Union,
|
||||
get_origin,
|
||||
get_args,
|
||||
List,
|
||||
Dict,
|
||||
Any,
|
||||
Tuple,
|
||||
Set,
|
||||
Annotated,
|
||||
FrozenSet,
|
||||
Callable,
|
||||
Type,
|
||||
)
|
||||
from types import UnionType, FunctionType, SimpleNamespace
|
||||
from copy import deepcopy, copy
|
||||
|
||||
# from pprint import pprint
|
||||
import math
|
||||
|
||||
from frozendict import deepfreeze
|
||||
from typeguard import check_type, TypeCheckError, CollectionCheckStrategy
|
||||
|
||||
ALLOWED_ANNOTATIONS = {
|
||||
@@ -31,46 +55,155 @@ ALLOWED_ANNOTATIONS = {
|
||||
}
|
||||
|
||||
ALLOWED_MODEL_FIELDS_TYPES = (str, int, float, complex, bool, bytes)
|
||||
ALLOWED_MODEL_FIELDS_CONTAINERS = (dict, list, set, frozenset, tuple)
|
||||
|
||||
|
||||
TV_ALLOWED_MODEL_FIELDS_TYPES = TypeVar("TV_ALLOWED_MODEL_FIELDS_TYPES", *ALLOWED_MODEL_FIELDS_TYPES, *ALLOWED_MODEL_FIELDS_CONTAINERS)
|
||||
# TV_ALLOWED_MODEL_FIELDS_TYPES = TypeVar("TV_ALLOWED_MODEL_FIELDS_TYPES", *ALLOWED_MODEL_FIELDS_TYPES, *ALLOWED_MODEL_FIELDS_CONTAINERS)
|
||||
|
||||
|
||||
class DABModelException(Exception): ...
|
||||
class DABModelException(Exception):
|
||||
"""DABModelException Exception class
|
||||
Base Exception for DABModelException class
|
||||
"""
|
||||
|
||||
class ReservedFieldName(Exception):
|
||||
"""DABModelException Exception class
|
||||
Base Exception for DABModelException class
|
||||
"""
|
||||
|
||||
class MultipleInheritanceForbidden(DABModelException):
|
||||
"""MultipleInheritanceForbidden Exception class
|
||||
Multiple inheritance is forbidden when using dabmodel
|
||||
"""
|
||||
|
||||
|
||||
class MultipleInheritanceForbidden(DABModelException): ...
|
||||
class BrokenInheritance(DABModelException):
|
||||
"""BrokenInheritance Exception class
|
||||
inheritance chain is broken
|
||||
"""
|
||||
|
||||
|
||||
class BrokenInheritance(DABModelException): ...
|
||||
class ReadOnlyField(DABModelException):
|
||||
"""ReadOnlyField Exception class
|
||||
The used Field is ReadOnly
|
||||
"""
|
||||
|
||||
|
||||
class ReadOnlyField(DABModelException): ...
|
||||
class NewFieldForbidden(DABModelException):
|
||||
"""NewFieldForbidden Exception class
|
||||
Field creation is forbidden
|
||||
"""
|
||||
|
||||
|
||||
class NewFieldForbidden(DABModelException): ...
|
||||
class InvalidFieldAnnotation(DABModelException):
|
||||
"""InvalidFieldAnnotation Exception class
|
||||
The field annotation is invalid
|
||||
"""
|
||||
|
||||
|
||||
class InvalidFieldAnnotation(DABModelException): ...
|
||||
class InvalidInitializerType(DABModelException):
|
||||
"""InvalidInitializerType Exception class
|
||||
The initializer is not a valid type
|
||||
"""
|
||||
|
||||
|
||||
class NotAnnotatedField(InvalidFieldAnnotation): ...
|
||||
class NotAnnotatedField(InvalidFieldAnnotation):
|
||||
"""NotAnnotatedField Exception class
|
||||
The Field is not Annotated
|
||||
"""
|
||||
|
||||
|
||||
class IncompletelyAnnotatedField(InvalidFieldAnnotation): ...
|
||||
class IncompletelyAnnotatedField(InvalidFieldAnnotation):
|
||||
"""IncompletelyAnnotatedField Exception class
|
||||
The field annotation is incomplete
|
||||
"""
|
||||
|
||||
|
||||
class ReadOnlyFieldAnnotation(DABModelException): ...
|
||||
class ReadOnlyFieldAnnotation(DABModelException):
|
||||
"""ReadOnlyFieldAnnotation Exception class
|
||||
Field annotation connot be modified
|
||||
"""
|
||||
|
||||
|
||||
class InvalidFieldValue(DABModelException): ...
|
||||
class InvalidFieldValue(DABModelException):
|
||||
"""InvalidFieldValue Exception class
|
||||
The Field value is invalid
|
||||
"""
|
||||
|
||||
|
||||
class NonExistingField(DABModelException):
|
||||
"""NonExistingField Exception class
|
||||
The given Field is non existing
|
||||
"""
|
||||
|
||||
|
||||
class ImportForbidden(DABModelException):
|
||||
"""ImportForbidden Exception class
|
||||
Imports are forbidden
|
||||
"""
|
||||
|
||||
|
||||
class FunctionForbidden(DABModelException):
|
||||
"""FunctionForbidden Exception class
|
||||
function call are forbidden
|
||||
"""
|
||||
|
||||
|
||||
ALLOWED_HELPERS_MATH = SimpleNamespace(
|
||||
sqrt=math.sqrt,
|
||||
floor=math.floor,
|
||||
ceil=math.ceil,
|
||||
trunc=math.trunc,
|
||||
fabs=math.fabs,
|
||||
copysign=math.copysign,
|
||||
hypot=math.hypot,
|
||||
exp=math.exp,
|
||||
log=math.log,
|
||||
log10=math.log10,
|
||||
sin=math.sin,
|
||||
cos=math.cos,
|
||||
tan=math.tan,
|
||||
atan2=math.atan2,
|
||||
radians=math.radians,
|
||||
degrees=math.degrees,
|
||||
)
|
||||
|
||||
ALLOWED_HELPERS_DEFAULT = {
|
||||
"math": ALLOWED_HELPERS_MATH,
|
||||
"print": print,
|
||||
# Numbers & reducers (pure, deterministic)
|
||||
"abs": abs,
|
||||
"round": round,
|
||||
"min": min,
|
||||
"max": max,
|
||||
"sum": sum,
|
||||
# Introspection-free basics
|
||||
"len": len,
|
||||
"sorted": sorted,
|
||||
# Basic constructors (for copy-on-write patterns)
|
||||
"tuple": tuple,
|
||||
"list": list,
|
||||
"dict": dict,
|
||||
"set": set,
|
||||
# Simple casts if they need to normalize types
|
||||
"int": int,
|
||||
"float": float,
|
||||
"str": str,
|
||||
"bool": bool,
|
||||
"bytes": bytes,
|
||||
"complex": complex,
|
||||
# Easy iteration helpers (optional but handy)
|
||||
"range": range,
|
||||
}
|
||||
|
||||
|
||||
def _blocked_import(*args, **kwargs):
|
||||
raise ImportForbidden("imports disabled in __initializer")
|
||||
|
||||
|
||||
def _resolve_annotation(ann):
|
||||
if isinstance(ann, str):
|
||||
# Safe eval against a **whitelist** only
|
||||
return eval(ann, {"__builtins__": {}}, ALLOWED_ANNOTATIONS)
|
||||
return eval(ann, {"__builtins__": {}}, ALLOWED_ANNOTATIONS) # pylint: disable=eval-used
|
||||
return ann
|
||||
|
||||
|
||||
@@ -88,90 +221,267 @@ def _peel_annotated(t: Any) -> Any:
|
||||
return t
|
||||
|
||||
|
||||
def __check_annotation_definition__(_type) -> bool:
|
||||
def _check_annotation_definition(_type) -> bool: # pylint: disable=too-complex,too-many-return-statements
|
||||
_type = _peel_annotated(_type)
|
||||
# handle Optional[] and Union[None,...]
|
||||
if (get_origin(_type) is Union or get_origin(_type) is UnionType) and type(None) in get_args(_type):
|
||||
return all([__check_annotation_definition__(_) for _ in get_args(_type) if _ is not type(None)])
|
||||
return all(_check_annotation_definition(_) for _ in get_args(_type) if _ is not type(None))
|
||||
|
||||
# handle other Union[...]
|
||||
if get_origin(_type) is Union or get_origin(_type) is UnionType:
|
||||
return all([__check_annotation_definition__(_) for _ in get_args(_type)])
|
||||
return all(_check_annotation_definition(_) for _ in get_args(_type))
|
||||
|
||||
# handle Dict[...]
|
||||
if get_origin(_type) is dict:
|
||||
inner = get_args(_type)
|
||||
if len(inner) != 2:
|
||||
raise IncompletelyAnnotatedField(f"Dict Annotation requires 2 inner definitions: {_type}")
|
||||
return _peel_annotated(inner[0]) in ALLOWED_MODEL_FIELDS_TYPES and __check_annotation_definition__(inner[1])
|
||||
return _peel_annotated(inner[0]) in ALLOWED_MODEL_FIELDS_TYPES and _check_annotation_definition(inner[1])
|
||||
|
||||
# handle Tuple[]
|
||||
if get_origin(_type) in [tuple]:
|
||||
inner_types = get_args(_type)
|
||||
if len(inner_types) == 0:
|
||||
raise IncompletelyAnnotatedField(f"Annotation requires inner definition: {_type}")
|
||||
if len(inner_types) == 2 and inner_types[1] is Ellipsis:
|
||||
return _check_annotation_definition(inner_types[0])
|
||||
return all(_check_annotation_definition(_) for _ in inner_types)
|
||||
|
||||
# handle Set[],Tuple[],FrozenSet[],List[]
|
||||
if get_origin(_type) in [set, frozenset, tuple, list]:
|
||||
inner_types = get_args(_type)
|
||||
if len(inner_types) == 0:
|
||||
raise IncompletelyAnnotatedField(f"Annotation requires inner definition: {_type}")
|
||||
return all([__check_annotation_definition__(_) for _ in inner_types])
|
||||
return all(_check_annotation_definition(_) for _ in inner_types)
|
||||
|
||||
if _type in ALLOWED_MODEL_FIELDS_TYPES:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Constraint: ...
|
||||
T_Field = TypeVar("T_Field")
|
||||
|
||||
|
||||
class BaseConstraint(Generic[T_Field]):
|
||||
"""BaseConstraint class
|
||||
Base class for Field's constraints
|
||||
"""
|
||||
|
||||
_bound_type: type
|
||||
|
||||
def __init__(self): ...
|
||||
|
||||
def check(self, value: T_Field) -> bool:
|
||||
"""Check if a Constraint is completed"""
|
||||
return True
|
||||
|
||||
|
||||
def _deepfreeze(value):
|
||||
"""recursive freeze helper function"""
|
||||
if isinstance(value, dict):
|
||||
return deepfreeze(value)
|
||||
elif isinstance(value, set):
|
||||
if isinstance(value, set):
|
||||
return frozenset(_deepfreeze(v) for v in value)
|
||||
elif isinstance(value, list):
|
||||
if isinstance(value, list):
|
||||
return tuple(_deepfreeze(v) for v in value)
|
||||
elif isinstance(value, tuple):
|
||||
if isinstance(value, tuple):
|
||||
return tuple(_deepfreeze(v) for v in value)
|
||||
return value
|
||||
|
||||
|
||||
class DABField(Generic[TV_ALLOWED_MODEL_FIELDS_TYPES]):
|
||||
def __init__(self, name: str, v: Optional[TV_ALLOWED_MODEL_FIELDS_TYPES], a: Any):
|
||||
self._constraints: List[Constraint] = []
|
||||
class DABFieldInfo:
|
||||
"""This Class allows to describe a Field in Appliance class"""
|
||||
|
||||
def __init__(self, *, doc: str = "", constraints: Optional[list[BaseConstraint]] = None):
|
||||
self._doc: str = doc
|
||||
self._constraints: list[BaseConstraint]
|
||||
if constraints is None:
|
||||
self._constraints = []
|
||||
else:
|
||||
self._constraints = constraints
|
||||
|
||||
@property
|
||||
def doc(self) -> str:
|
||||
"""Returns Field's documentation"""
|
||||
return self._doc
|
||||
|
||||
@property
|
||||
def constraints(self) -> list[BaseConstraint[Any]]:
|
||||
"""Returns Field's constraints"""
|
||||
return self._constraints
|
||||
|
||||
|
||||
class DABField(Generic[T_Field]):
|
||||
"""This class describe a Field in Schema"""
|
||||
|
||||
def __init__(self, name: str, v: Optional[T_Field], a: Any, i: DABFieldInfo):
|
||||
self._name: str = name
|
||||
self._source: Optional[type] = None
|
||||
self._default_value: Optional[TV_ALLOWED_MODEL_FIELDS_TYPES] = v
|
||||
self._value: Optional[TV_ALLOWED_MODEL_FIELDS_TYPES] = v
|
||||
self._default_value: Optional[T_Field] = v
|
||||
self._value: Optional[T_Field] = v
|
||||
self._annotations: Any = a
|
||||
self._documentation: str = ""
|
||||
|
||||
def add_documentation(self, d: str) -> None:
|
||||
self._documentation = d
|
||||
self._info: DABFieldInfo = i
|
||||
self._constraints: List[BaseConstraint[Any]] = i.constraints
|
||||
|
||||
def add_source(self, s: type) -> None:
|
||||
"""Adds source Appliance to the Field"""
|
||||
self._source = s
|
||||
|
||||
def add_constraint(self, c: Constraint) -> None:
|
||||
@property
|
||||
def doc(self) -> str:
|
||||
"""Returns Field's documentation"""
|
||||
return self._info.doc
|
||||
|
||||
def add_constraint(self, c: BaseConstraint) -> None:
|
||||
"""Adds constraint to the Field"""
|
||||
self._constraints.append(c)
|
||||
|
||||
def update_value(self, v: Optional[TV_ALLOWED_MODEL_FIELDS_TYPES] = None) -> None:
|
||||
self._value = v
|
||||
@property
|
||||
def constraints(self) -> list[BaseConstraint]:
|
||||
"""Returns Field's constraint"""
|
||||
return self._info.constraints
|
||||
|
||||
def render(self) -> TV_ALLOWED_MODEL_FIELDS_TYPES:
|
||||
return _deepfreeze(self._value)
|
||||
|
||||
def render_default(self) -> TV_ALLOWED_MODEL_FIELDS_TYPES:
|
||||
@property
|
||||
def default_value(self) -> Any:
|
||||
"""Returns Field's default value (frozen)"""
|
||||
return _deepfreeze(self._default_value)
|
||||
|
||||
def get_annotation(self) -> Any:
|
||||
def update_value(self, v: Optional[T_Field] = None) -> None:
|
||||
"""Updates Field's value"""
|
||||
self._value = v
|
||||
|
||||
@property
|
||||
def value(self) -> Any:
|
||||
"""Returns Field's value (frosen)"""
|
||||
return _deepfreeze(self._value)
|
||||
|
||||
@property
|
||||
def raw_value(self) -> Optional[T_Field]:
|
||||
"""Returns Field's value"""
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def annotations(self) -> Any:
|
||||
"""Returns Field's annotation"""
|
||||
return self._annotations
|
||||
|
||||
|
||||
class FrozenDABField(Generic[T_Field]):
|
||||
"""FrozenDABField class
|
||||
a read-only proxy of a Field
|
||||
"""
|
||||
|
||||
def __init__(self, inner_field: DABField):
|
||||
self._inner_field = inner_field
|
||||
|
||||
@property
|
||||
def doc(self) -> str:
|
||||
"""Returns Field's documentation (frozen)"""
|
||||
return _deepfreeze(self._inner_field.doc)
|
||||
|
||||
@property
|
||||
def constraints(self) -> tuple[BaseConstraint]:
|
||||
"""Returns Field's constraint (frozen)"""
|
||||
return _deepfreeze(self._inner_field.constraints)
|
||||
|
||||
@property
|
||||
def default_value(self) -> Any:
|
||||
"""Returns Field's default value (frozen)"""
|
||||
return self._inner_field.default_value
|
||||
|
||||
@property
|
||||
def value(self) -> Any:
|
||||
"""Returns Field's value (frosen)"""
|
||||
return self._inner_field.value
|
||||
|
||||
@property
|
||||
def annotations(self) -> Any:
|
||||
"""Returns Field's annotation (frozen)"""
|
||||
return _deepfreeze(self._inner_field.annotations)
|
||||
|
||||
|
||||
class ModelSpecView:
|
||||
"""ModelSpecView class
|
||||
A class that will act as fake BaseElement proxy to allow setting values"""
|
||||
|
||||
__slots__ = ("_vals", "_types", "_touched", "_name", "_module")
|
||||
|
||||
def __init__(self, values: dict[str, Any], types_map: dict[str, type], name: str, module: str):
|
||||
self._name: str
|
||||
self._vals: dict[str, Any]
|
||||
self._types: dict[str, type]
|
||||
self._touched: set
|
||||
self._module: str
|
||||
object.__setattr__(self, "_vals", dict(values))
|
||||
object.__setattr__(self, "_types", types_map)
|
||||
object.__setattr__(self, "_name", name)
|
||||
object.__setattr__(self, "_module", module)
|
||||
|
||||
@property
|
||||
def __name__(self) -> str:
|
||||
"""returns proxified class' name"""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def __module__(self) -> str:
|
||||
"""returns proxified module's name"""
|
||||
return self._module
|
||||
|
||||
@__module__.setter
|
||||
def __module__(self, value: str):
|
||||
pass
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""internal proxy getattr"""
|
||||
if name not in self._types:
|
||||
raise AttributeError(f"Unknown field {name}")
|
||||
return self._vals[name]
|
||||
|
||||
def __setattr__(self, name: str, value: Any):
|
||||
"""internal proxy setattr"""
|
||||
if name not in self._types:
|
||||
raise NonExistingField(f"Cannot set unknown field {name}")
|
||||
T = self._types[name]
|
||||
|
||||
try:
|
||||
check_type(
|
||||
value,
|
||||
T,
|
||||
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
|
||||
)
|
||||
except TypeCheckError as exp:
|
||||
raise InvalidFieldValue(f"Field <{name}> value is not of expected type {T}.") from exp
|
||||
|
||||
self._vals[name] = value
|
||||
|
||||
def export(self) -> dict:
|
||||
"""exports all proxified values"""
|
||||
return dict(self._vals)
|
||||
|
||||
|
||||
T_Meta = TypeVar("T_Meta", bound="BaseMeta")
|
||||
T_BE = TypeVar("T_BE", bound="BaseElement")
|
||||
|
||||
|
||||
class BaseMeta(type):
|
||||
def __new__(mcls, name, bases, namespace):
|
||||
"""metaclass to use to build BaseElement"""
|
||||
|
||||
modified_field: Dict[str, Any] = {}
|
||||
new_fields: Dict[str, DABField[Any]] = {}
|
||||
initializer: Optional[Callable[..., Any]] = None
|
||||
__DABSchema__: dict[str, Any] = {}
|
||||
|
||||
@classmethod
|
||||
def pre_check(
|
||||
mcs: type["BaseMeta"], name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any] # pylint: disable=unused-argument
|
||||
) -> None:
|
||||
"""early BaseElement checks"""
|
||||
# print("__NEW__ Defining:", name, "with keys:", list(namespace))
|
||||
|
||||
if len(bases) > 1:
|
||||
raise MultipleInheritanceForbidden("Multiple inheritance is not supported by dabmodel")
|
||||
elif len(bases) == 0: # base class (BaseElement)
|
||||
namespace["__DABSchema__"] = dict()
|
||||
if len(bases) == 0: # base class (BaseElement)
|
||||
namespace["__DABSchema__"] = {}
|
||||
else: # standard inheritance
|
||||
# check class tree origin
|
||||
if "__DABSchema__" not in dir(bases[0]):
|
||||
@@ -184,114 +494,245 @@ class BaseMeta(type):
|
||||
for _funknown in [_ for _ in namespace["__annotations__"] if _ not in namespace.keys()]:
|
||||
namespace[_funknown] = None
|
||||
|
||||
@classmethod
|
||||
def pre_processing(mcs: type["BaseMeta"], name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any]):
|
||||
"""preprocessing BaseElement"""
|
||||
# iterating new and modified fields
|
||||
modified_field: Dict[str, Any] = {}
|
||||
new_fields: Dict[str, DABField] = {}
|
||||
mcs.modified_field = {}
|
||||
mcs.new_fields = {}
|
||||
mcs.initializer = None
|
||||
initializer_name: Optional[str] = None
|
||||
for _fname, _fvalue in namespace.items():
|
||||
if _fname.startswith("__"):
|
||||
if _fname == f"_{name}__initializer" or (name.startswith("_") and _fname == "__initializer"):
|
||||
if not isinstance(_fvalue, classmethod):
|
||||
raise InvalidInitializerType()
|
||||
mcs.initializer = _fvalue.__func__
|
||||
if name.startswith("_"):
|
||||
initializer_name = "__initializer"
|
||||
else:
|
||||
initializer_name = f"_{name}__initializer"
|
||||
elif _fname.startswith("__"):
|
||||
pass
|
||||
elif _fname == "Constraints" and type(_fvalue) is type:
|
||||
...
|
||||
# print("FOUND Constraints")
|
||||
else:
|
||||
# print(f"Parsing Field: {_fname} / {_fvalue}")
|
||||
# Modified fields
|
||||
if len(bases) == 1 and _fname in namespace["__DABSchema__"].keys():
|
||||
# print(f"Modified field: {_fname}")
|
||||
if _fname in namespace["__annotations__"]:
|
||||
raise ReadOnlyFieldAnnotation("annotations cannot be modified on derived classes")
|
||||
try:
|
||||
check_type(
|
||||
_fvalue,
|
||||
namespace["__DABSchema__"][_fname].get_annotation(),
|
||||
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
|
||||
)
|
||||
except TypeCheckError as exp:
|
||||
raise InvalidFieldValue(
|
||||
f"Field <{_fname}> New Field value is not of expected type {bases[0].__annotations__[_fname]}."
|
||||
) from exp
|
||||
modified_field[_fname] = _fvalue
|
||||
# New fieds
|
||||
else:
|
||||
# print(f"New field: {_fname}")
|
||||
# print(f"type is: {type(_fvalue)}")
|
||||
# print(f"value is: {_fvalue}")
|
||||
|
||||
# check if field is annotated
|
||||
if "__annotations__" not in namespace or _fname not in namespace["__annotations__"]:
|
||||
raise NotAnnotatedField(f"Every dabmodel Fields must be annotated ({_fname})")
|
||||
|
||||
# check if annotation is allowed
|
||||
if isinstance(namespace["__annotations__"][_fname], str):
|
||||
namespace["__annotations__"][_fname] = _resolve_annotation(namespace["__annotations__"][_fname])
|
||||
|
||||
if not __check_annotation_definition__(namespace["__annotations__"][_fname]):
|
||||
raise InvalidFieldAnnotation(f"Field <{_fname}> has not an allowed or valid annotation.")
|
||||
|
||||
# print(f"annotation is: {namespace['__annotations__'][_fname]}")
|
||||
# check if value is valid
|
||||
try:
|
||||
check_type(
|
||||
_fvalue, namespace["__annotations__"][_fname], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS
|
||||
)
|
||||
except TypeCheckError as exp:
|
||||
raise InvalidFieldValue(
|
||||
f"Value of Field <{_fname}> is not of expected type {namespace['__annotations__'][_fname]}."
|
||||
) from exp
|
||||
new_fields[_fname] = DABField(_fname, _fvalue, namespace["__annotations__"][_fname])
|
||||
# namespace[_fname].add_documentation()
|
||||
|
||||
if len(bases) == 1 and _fname in namespace["__DABSchema__"].keys(): # Modified fields
|
||||
mcs.pre_processing_modified_fields(name, bases, namespace, _fname, _fvalue)
|
||||
else: # New fieds
|
||||
mcs.pre_processing_new_fields(name, bases, namespace, _fname, _fvalue)
|
||||
# removing modified fields from class (will add them back later)
|
||||
for _fname in new_fields.keys():
|
||||
for _fname in mcs.new_fields:
|
||||
del namespace[_fname]
|
||||
for _fname in modified_field.keys():
|
||||
for _fname in mcs.modified_field:
|
||||
del namespace[_fname]
|
||||
if mcs.initializer is not None and initializer_name is not None:
|
||||
del namespace[initializer_name]
|
||||
|
||||
orig_setattr = namespace.get("__setattr__", object.__setattr__)
|
||||
@classmethod
|
||||
def pre_processing_modified_fields(
|
||||
mcs: type["BaseMeta"], name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any], _fname: str, _fvalue: Any
|
||||
): # pylint: disable=unused-argument
|
||||
"""preprocessing BaseElement modified Fields"""
|
||||
# print(f"Modified field: {_fname}")
|
||||
if "__annotations__" in namespace and _fname in namespace["__annotations__"]:
|
||||
raise ReadOnlyFieldAnnotation("annotations cannot be modified on derived classes")
|
||||
try:
|
||||
check_type(
|
||||
_fvalue,
|
||||
namespace["__DABSchema__"][_fname].annotations,
|
||||
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
|
||||
)
|
||||
except TypeCheckError as exp:
|
||||
raise InvalidFieldValue(
|
||||
f"Field <{_fname}> New Field value is not of expected type {bases[0].__annotations__[_fname]}."
|
||||
) from exp
|
||||
mcs.modified_field[_fname] = _fvalue
|
||||
|
||||
def guarded_setattr(self, key, value):
|
||||
if key.startswith("_"): # allow private and dunder attrs
|
||||
return orig_setattr(self, key, value)
|
||||
# block writes after init if key is readonly
|
||||
if key in self.__DABSchema__.keys():
|
||||
if hasattr(self, key):
|
||||
raise ReadOnlyField(f"{key} is read-only")
|
||||
else:
|
||||
raise NewFieldForbidden(f"creating new fields is not allowed")
|
||||
@classmethod
|
||||
def pre_processing_new_fields(
|
||||
mcs: type["BaseMeta"], name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any], _fname: str, _fvalue: Any
|
||||
): # pylint: disable=unused-argument
|
||||
"""preprocessing BaseElement new Fields"""
|
||||
# print(f"New field: {_fname}")
|
||||
# print(f"type is: {type(_fvalue)}")
|
||||
# print(f"value is: {_fvalue}")
|
||||
|
||||
return orig_setattr(self, key, value)
|
||||
# check if field is annotated
|
||||
if "__annotations__" not in namespace or _fname not in namespace["__annotations__"]:
|
||||
raise NotAnnotatedField(f"Every dabmodel Fields must be annotated ({_fname})")
|
||||
|
||||
namespace["__setattr__"] = guarded_setattr
|
||||
# check if annotation is allowed
|
||||
if isinstance(namespace["__annotations__"][_fname], str):
|
||||
namespace["__annotations__"][_fname] = _resolve_annotation(namespace["__annotations__"][_fname])
|
||||
|
||||
cls = super().__new__(mcls, name, bases, namespace)
|
||||
if not _check_annotation_definition(namespace["__annotations__"][_fname]):
|
||||
raise InvalidFieldAnnotation(f"Field <{_fname}> has not an allowed or valid annotation.")
|
||||
|
||||
for _fname, _fvalue in modified_field.items():
|
||||
_finfo: DABFieldInfo = DABFieldInfo()
|
||||
origin = get_origin(namespace["__annotations__"][_fname])
|
||||
tname = getattr(origin, "__name__", "") or getattr(origin, "__qualname__", "") or str(origin)
|
||||
if "Annotated" in tname:
|
||||
args = get_args(namespace["__annotations__"][_fname])
|
||||
if args:
|
||||
if len(args) > 2:
|
||||
raise InvalidFieldAnnotation(f"Field <{_fname}> had invalid Annotated value.")
|
||||
if len(args) == 2 and not isinstance(args[1], DABFieldInfo):
|
||||
raise InvalidFieldAnnotation("Only DABFieldInfo object is allowed as Annotated data.")
|
||||
|
||||
_finfo = args[1]
|
||||
|
||||
# print(f"annotation is: {namespace['__annotations__'][_fname]}")
|
||||
# check if value is valid
|
||||
try:
|
||||
check_type(_fvalue, namespace["__annotations__"][_fname], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS)
|
||||
except TypeCheckError as exp:
|
||||
raise InvalidFieldValue(f"Value of Field <{_fname}> is not of expected type {namespace['__annotations__'][_fname]}.") from exp
|
||||
mcs.new_fields[_fname] = DABField(_fname, _fvalue, namespace["__annotations__"][_fname], _finfo)
|
||||
|
||||
@classmethod
|
||||
def call_initializer(
|
||||
mcs: type["BaseMeta"], cls, name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any] # pylint: disable=unused-argument
|
||||
):
|
||||
"""BaseElement initializer processing"""
|
||||
if mcs.initializer is not None:
|
||||
init_fieldvalues = {}
|
||||
init_fieldtypes = {}
|
||||
for _fname, _fvalue in cls.__DABSchema__.items():
|
||||
init_fieldvalues[_fname] = deepcopy(_fvalue.raw_value)
|
||||
init_fieldtypes[_fname] = _fvalue.annotations
|
||||
fakecls = ModelSpecView(init_fieldvalues, init_fieldtypes, cls.__name__, cls.__module__)
|
||||
safe_globals = {"__builtins__": {"__import__": _blocked_import}, **ALLOWED_HELPERS_DEFAULT}
|
||||
if mcs.initializer.__code__.co_freevars:
|
||||
raise FunctionForbidden("__initializer must not use closures")
|
||||
safe_initializer = FunctionType(
|
||||
mcs.initializer.__code__,
|
||||
safe_globals,
|
||||
name=mcs.initializer.__name__,
|
||||
argdefs=mcs.initializer.__defaults__,
|
||||
closure=None,
|
||||
)
|
||||
safe_initializer(fakecls) # pylint: disable=not-callable
|
||||
for _fname, _fvalue in fakecls.export().items():
|
||||
try:
|
||||
check_type(_fvalue, cls.__DABSchema__[_fname].annotations, collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS)
|
||||
except TypeCheckError as exp:
|
||||
raise InvalidFieldValue(
|
||||
f"Value of Field <{_fname}> is not of expected type {namespace['__annotations__'][_fname]}."
|
||||
) from exp
|
||||
cls.__DABSchema__[_fname].update_value(_fvalue)
|
||||
|
||||
def __new__(mcs: type["BaseMeta"], name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any]) -> Type:
|
||||
"""BaseElement new class"""
|
||||
mcs.pre_check(name, bases, namespace)
|
||||
mcs.pre_processing(name, bases, namespace)
|
||||
|
||||
_cls = super().__new__(mcs, name, bases, namespace)
|
||||
|
||||
mcs.save_values(_cls, name, bases, namespace)
|
||||
mcs.call_initializer(_cls, name, bases, namespace)
|
||||
|
||||
return _cls
|
||||
|
||||
@classmethod
|
||||
def save_values(
|
||||
mcs: type["BaseMeta"], cls, name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any] # pylint: disable=unused-argument
|
||||
):
|
||||
for _fname, _fvalue in mcs.modified_field.items():
|
||||
cls.__DABSchema__[_fname] = deepcopy(bases[0].__DABSchema__[_fname])
|
||||
cls.__DABSchema__[_fname].update_value(_fvalue)
|
||||
|
||||
for _fname, _fvalue in new_fields.items():
|
||||
_fvalue.add_source(cls)
|
||||
for _fname, _fvalue in mcs.new_fields.items():
|
||||
_fvalue.add_source(mcs)
|
||||
cls.__DABSchema__[_fname] = _fvalue
|
||||
|
||||
return cls
|
||||
|
||||
def __call__(cls, *args, **kw):
|
||||
|
||||
def __call__(cls, *args: Any, **kw: Any): # intentionally untyped
|
||||
"""BaseElement new instance"""
|
||||
obj = super().__call__(*args, **kw)
|
||||
|
||||
for _fname in cls.__DABSchema__.keys():
|
||||
setattr(obj, _fname, cls.__DABSchema__[_fname].render())
|
||||
# obj.__DABSchema__ = deepfreeze(obj.__DABSchema__)
|
||||
# setattr(obj, "__DABSchema__", deepfreeze(obj.__DABSchema__))
|
||||
return obj
|
||||
for _fname, _fvalue in cls.__DABSchema__.items():
|
||||
setattr(obj, _fname, _fvalue.value)
|
||||
inst_schema: dict[str, Any] = {}
|
||||
for _fname, _fvalue in cls.__DABSchema__.items():
|
||||
inst_schema[_fname] = FrozenDABField(_fvalue)
|
||||
#print(inst_schema)
|
||||
setattr(obj, "__DABSchema__", inst_schema)
|
||||
|
||||
|
||||
cls.modify_object(obj)
|
||||
cls.install_guard(obj)
|
||||
return obj
|
||||
|
||||
def modify_object(self,obj):
|
||||
pass
|
||||
|
||||
def install_guard(self, obj):
|
||||
orig_setattr = getattr(self,"__setattr__")
|
||||
|
||||
def guarded_setattr(_self, key: str, value: Any):
|
||||
if key.startswith("_"): # allow private and dunder attrs
|
||||
return orig_setattr(_self, key, value)
|
||||
# block writes after init if key is readonly
|
||||
if key in _self.__DABSchema__.keys():
|
||||
if hasattr(_self, key):
|
||||
raise ReadOnlyField(f"{key} is read-only")
|
||||
else:
|
||||
raise NewFieldForbidden("creating new fields is not allowed")
|
||||
|
||||
return orig_setattr(_self, key, value)
|
||||
|
||||
setattr(self, "__setattr__", guarded_setattr)
|
||||
|
||||
class BaseElement(metaclass=BaseMeta):
|
||||
"""BaseElement class
|
||||
Base class to apply metaclass and set common Fields.
|
||||
"""
|
||||
|
||||
class BaseMetaFeature(BaseMeta):
|
||||
pass
|
||||
|
||||
class BaseFeature(BaseElement,metaclass=BaseMetaFeature):
|
||||
"""BaseFeature class
|
||||
Base class for Appliance's Features.
|
||||
Features are optional traits of an appliance.
|
||||
"""
|
||||
|
||||
class BaseFeature(BaseElement):
|
||||
pass
|
||||
|
||||
|
||||
class BaseAppliance(BaseElement):
|
||||
pass
|
||||
class BaseMetaAppliance(BaseMeta):
|
||||
|
||||
@classmethod
|
||||
def pre_processing(mcs: type["BaseMeta"], name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any]):
|
||||
mcs.features: dict[str,type[BaseFeature]] = {}
|
||||
super().pre_processing(name,bases,namespace)
|
||||
for _ftname in mcs.features.keys():
|
||||
del namespace[_ftname]
|
||||
|
||||
@classmethod
|
||||
def pre_processing_new_fields(
|
||||
mcs: type["BaseMeta"], name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any], _fname: str, _fvalue: Any
|
||||
): # pylint: disable=unused-argument
|
||||
"""preprocessing BaseElement new Fields"""
|
||||
if _fname == "features":
|
||||
raise ReservedFieldName("feature is a reserved field name")
|
||||
elif _fname =="get_features":
|
||||
pass
|
||||
elif isinstance(_fvalue,BaseMetaFeature):
|
||||
mcs.features[_fname]=_fvalue
|
||||
else:
|
||||
super().pre_processing_new_fields(name,bases,namespace,_fname,_fvalue)
|
||||
|
||||
@classmethod
|
||||
def save_values(
|
||||
mcs: type["BaseMeta"], cls, name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any] # pylint: disable=unused-argument
|
||||
):
|
||||
super().save_values(cls,name,bases,namespace)
|
||||
cls.__DABFeatures__ = mcs.features
|
||||
|
||||
def modify_object(self, obj): # intentionally untyped
|
||||
for _ftname,_ftvalue in self.features.items():
|
||||
instft = _ftvalue()
|
||||
setattr(self, _ftname,instft )
|
||||
self.__DABFeatures__[_ftname] = instft
|
||||
|
||||
class BaseAppliance(BaseElement,metaclass=BaseMetaAppliance):
|
||||
"""BaseFeature class
|
||||
Base class for Appliance.
|
||||
An appliance is a server configuration / image that is built using appliance's code and Fields.
|
||||
"""
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
"""library's internal tools"""
|
||||
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
|
||||
class DABJSONEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, UUID):
|
||||
# if the obj is uuid, we simply return the value of uuid
|
||||
return obj.hex
|
||||
elif isinstance(obj, datetime):
|
||||
return str(obj)
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
"""allows to JSON encode non supported data type"""
|
||||
|
||||
def default(self, o):
|
||||
if isinstance(o, UUID):
|
||||
# if the o is uuid, we simply return the value of uuid
|
||||
return o.hex
|
||||
if isinstance(o, datetime):
|
||||
return str(o)
|
||||
return json.JSONEncoder.default(self, o)
|
||||
|
||||
@@ -15,6 +15,7 @@ from pathlib import Path
|
||||
import textwrap
|
||||
from typing import List, Optional, Dict, Union, Tuple, Set, FrozenSet, TypeVar, Generic, Any, Annotated
|
||||
from pprint import pprint
|
||||
from frozendict import frozendict
|
||||
|
||||
print(__name__)
|
||||
print(__package__)
|
||||
@@ -26,6 +27,10 @@ testdir_path = Path(__file__).parent.resolve()
|
||||
chdir(testdir_path.parent.resolve())
|
||||
|
||||
|
||||
def test_initializer_safe_testfc():
|
||||
eval("print('hi')")
|
||||
|
||||
|
||||
class TestConfigWithoutEnabledFlag(unittest.TestCase):
|
||||
def setUp(self):
|
||||
print("\n->", unittest.TestCase.id(self))
|
||||
@@ -175,13 +180,6 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase):
|
||||
class _(dm.BaseAppliance):
|
||||
_ = "default value"
|
||||
|
||||
def test_annotated(self):
|
||||
"""Testing first appliance level, and Field types (annotated one)"""
|
||||
|
||||
# class can be created if annotation is a string
|
||||
class Appliance1(dm.BaseAppliance):
|
||||
StrVar: Annotated[str, "my string"] = "default value"
|
||||
|
||||
def test_optionnal(self):
|
||||
"""Testing first appliance level, and Field types (Optionnal annotations)"""
|
||||
|
||||
@@ -221,7 +219,7 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase):
|
||||
self.immutable_vars__test_field(app2, "StrVar5", "default value", "123")
|
||||
self.immutable_vars__test_field(app2, "StrVar6", None, "123")
|
||||
|
||||
@unittest.skip
|
||||
# @unittest.skip
|
||||
def test_containers__set(self):
|
||||
"""Testing first appliance level, and Field types (Set)"""
|
||||
|
||||
@@ -300,7 +298,7 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase):
|
||||
res = subprocess.run([sys.executable, "-c", code], env=env)
|
||||
self.assertEqual(res.returncode, 2)
|
||||
|
||||
@unittest.skip
|
||||
# @unittest.skip
|
||||
def test_containers__frozenset(self):
|
||||
"""Testing first appliance level, and Field types (FrozenSet)"""
|
||||
|
||||
@@ -492,6 +490,7 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase):
|
||||
testVar4: "Tuple[str,...]" = ("a", "c")
|
||||
testVar5: tuple[str, ...] = ("a", "b")
|
||||
testVar6: "tuple[str,...]" = ("a", "b")
|
||||
testVar7: Tuple[int, str] = (1, "b")
|
||||
# testVar7: Tuple[Union[int, str]] = (1, 2, 3, "one", "two", "three")
|
||||
|
||||
app1 = Appliance1()
|
||||
@@ -502,7 +501,7 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase):
|
||||
self.immutable_vars__test_field(app1, "testVar4", ("a", "c"), ("h", "e"))
|
||||
self.immutable_vars__test_field(app1, "testVar5", ("a", "b"), ("h", "c"))
|
||||
self.immutable_vars__test_field(app1, "testVar6", ("a", "b"), ("h", "c"))
|
||||
# self.immutable_vars__test_field(app1, "testVar7", (1, 2, 3, "one", "two", "three"), ("h", "c"))
|
||||
self.immutable_vars__test_field(app1, "testVar7", (1, "b"), (7, "h"))
|
||||
|
||||
# must work
|
||||
sorted(app1.testVar)
|
||||
@@ -527,7 +526,673 @@ class TestConfigWithoutEnabledFlag(unittest.TestCase):
|
||||
class _(dm.BaseAppliance):
|
||||
_: "Tuple" = (1, 2)
|
||||
|
||||
with self.assertRaises(dm.InvalidFieldValue):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: "Tuple[int,...]" = (1, "a")
|
||||
|
||||
with self.assertRaises(dm.InvalidFieldValue):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: "tuple[int,...]" = (1, "a")
|
||||
|
||||
def check_immutable_fields_schema(
|
||||
self, appliance: dm.BaseAppliance, field_name: str, expected_value: str, expected_default_value: str, expected_type: type
|
||||
):
|
||||
self.assertIn(field_name, appliance.__DABSchema__)
|
||||
self.assertIn("doc", dir(appliance.__DABSchema__[field_name]))
|
||||
self.assertEqual(appliance.__DABSchema__[field_name].doc, "")
|
||||
self.assertIn("annotations", dir(appliance.__DABSchema__[field_name]))
|
||||
self.assertEqual(appliance.__DABSchema__[field_name].annotations, expected_type)
|
||||
self.assertIn("value", dir(appliance.__DABSchema__[field_name]))
|
||||
self.assertEqual(appliance.__DABSchema__[field_name].value, expected_value)
|
||||
self.assertIn("default_value", dir(appliance.__DABSchema__[field_name]))
|
||||
self.assertEqual(appliance.__DABSchema__[field_name].default_value, expected_default_value)
|
||||
self.assertIn("constraints", dir(appliance.__DABSchema__[field_name]))
|
||||
self.assertEqual(appliance.__DABSchema__[field_name].constraints, ())
|
||||
|
||||
def test_immutable_fields_schema(self):
|
||||
"""Testing first appliance level, and Field types (annotated)"""
|
||||
|
||||
# class can be created
|
||||
class Appliance1(dm.BaseAppliance):
|
||||
StrVar: "str" = "default value"
|
||||
StrVar2: "str" = "default value2"
|
||||
VarInt: "int" = 12
|
||||
VarInt2: "int" = 21
|
||||
VarFloat: "float" = 12.1
|
||||
VarFloat2: "float" = 21.2
|
||||
VarComplex: "complex" = complex(3, 5)
|
||||
VarComplex2: "complex" = complex(8, 6)
|
||||
VarBool: "bool" = True
|
||||
VarBool2: "bool" = False
|
||||
VarBytes: "bytes" = bytes.fromhex("2Ef0 F1f2 ")
|
||||
VarBytes2: "bytes" = bytes.fromhex("2ff0 F7f2 ")
|
||||
|
||||
app1 = Appliance1()
|
||||
|
||||
self.assertIn("__DABSchema__", dir(app1))
|
||||
self.assertIn("__DABSchema__", app1.__dict__)
|
||||
|
||||
self.check_immutable_fields_schema(app1, "StrVar", "default value", "default value", str)
|
||||
self.check_immutable_fields_schema(app1, "StrVar2", "default value2", "default value2", str)
|
||||
self.check_immutable_fields_schema(app1, "VarInt", 12, 12, int)
|
||||
self.check_immutable_fields_schema(app1, "VarInt2", 21, 21, int)
|
||||
self.check_immutable_fields_schema(app1, "VarFloat", 12.1, 12.1, float)
|
||||
self.check_immutable_fields_schema(app1, "VarFloat2", 21.2, 21.2, float)
|
||||
self.check_immutable_fields_schema(app1, "VarComplex", complex(3, 5), complex(3, 5), complex)
|
||||
self.check_immutable_fields_schema(app1, "VarComplex2", complex(8, 6), complex(8, 6), complex)
|
||||
self.check_immutable_fields_schema(app1, "VarBool", True, True, bool)
|
||||
self.check_immutable_fields_schema(app1, "VarBool2", False, False, bool)
|
||||
self.check_immutable_fields_schema(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes)
|
||||
self.check_immutable_fields_schema(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("2ff0 F7f2 "), bytes)
|
||||
|
||||
def test_container_field_schema(self):
|
||||
"""Testing first appliance level, and Field types (annotated)"""
|
||||
|
||||
# class can be created
|
||||
class Appliance1(dm.BaseAppliance):
|
||||
ListStr: list[str] = ["val1", "val2"]
|
||||
ListStr2: "list[str]" = ["val3", "val4"]
|
||||
Dict1: dict[int, float] = {1: 1.1, 4: 7.6, 91: 23.6}
|
||||
Dict2: "dict[str, str]" = {"1": "1.1", "4": "7.6", "91": "23.6"}
|
||||
Tuple1: "tuple[str,...]" = ("a", "c")
|
||||
Tuple2: tuple[str, ...] = ("a", "b")
|
||||
FrozenSet1: frozenset[int] = frozenset({1, 2})
|
||||
FrozenSet2: "frozenset[int]" = frozenset({1, 2})
|
||||
Set1: set[int] = set({1, 2})
|
||||
Set2: "set[int]" = set({1, 2})
|
||||
|
||||
app1 = Appliance1()
|
||||
|
||||
self.assertIn("__DABSchema__", dir(app1))
|
||||
self.assertIn("__DABSchema__", app1.__dict__)
|
||||
|
||||
self.check_immutable_fields_schema(app1, "ListStr", ("val1", "val2"), ("val1", "val2"), list[str])
|
||||
self.check_immutable_fields_schema(app1, "ListStr2", ("val3", "val4"), ("val3", "val4"), list[str])
|
||||
self.check_immutable_fields_schema(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, dict[int, float])
|
||||
self.check_immutable_fields_schema(
|
||||
app1, "Dict2", {"1": "1.1", "4": "7.6", "91": "23.6"}, {"1": "1.1", "4": "7.6", "91": "23.6"}, dict[str, str]
|
||||
)
|
||||
self.check_immutable_fields_schema(app1, "Tuple1", ("a", "c"), ("a", "c"), tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app1, "Tuple2", ("a", "b"), ("a", "b"), tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), frozenset[int])
|
||||
self.check_immutable_fields_schema(app1, "FrozenSet2", frozenset({1, 2}), frozenset({1, 2}), frozenset[int])
|
||||
self.check_immutable_fields_schema(app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), set[int])
|
||||
self.check_immutable_fields_schema(app1, "Set2", frozenset({1, 2}), frozenset({1, 2}), set[int])
|
||||
|
||||
# same test with Typing types (list -> List ...)
|
||||
# class can be created
|
||||
class Appliance1(dm.BaseAppliance):
|
||||
ListStr: List[str] = ["val1", "val2"]
|
||||
ListStr2: "List[str]" = ["val3", "val4"]
|
||||
Dict1: Dict[int, float] = {1: 1.1, 4: 7.6, 91: 23.6}
|
||||
Dict2: "Dict[str, str]" = {"1": "1.1", "4": "7.6", "91": "23.6"}
|
||||
Tuple1: "Tuple[str,...]" = ("a", "c")
|
||||
Tuple2: Tuple[str, ...] = ("a", "b")
|
||||
FrozenSet1: FrozenSet[int] = frozenset({1, 2})
|
||||
FrozenSet2: "FrozenSet[int]" = frozenset({1, 2})
|
||||
Set1: Set[int] = set({1, 2})
|
||||
Set2: "Set[int]" = set({1, 2})
|
||||
|
||||
app1 = Appliance1()
|
||||
|
||||
self.assertIn("__DABSchema__", dir(app1))
|
||||
self.assertIn("__DABSchema__", app1.__dict__)
|
||||
|
||||
self.check_immutable_fields_schema(app1, "ListStr", ("val1", "val2"), ("val1", "val2"), List[str])
|
||||
self.check_immutable_fields_schema(app1, "ListStr2", ("val3", "val4"), ("val3", "val4"), List[str])
|
||||
self.check_immutable_fields_schema(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, Dict[int, float])
|
||||
self.check_immutable_fields_schema(
|
||||
app1, "Dict2", {"1": "1.1", "4": "7.6", "91": "23.6"}, {"1": "1.1", "4": "7.6", "91": "23.6"}, Dict[str, str]
|
||||
)
|
||||
self.check_immutable_fields_schema(app1, "Tuple1", ("a", "c"), ("a", "c"), Tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app1, "Tuple2", ("a", "b"), ("a", "b"), Tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), FrozenSet[int])
|
||||
self.check_immutable_fields_schema(app1, "FrozenSet2", frozenset({1, 2}), frozenset({1, 2}), FrozenSet[int])
|
||||
self.check_immutable_fields_schema(app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), Set[int])
|
||||
self.check_immutable_fields_schema(app1, "Set2", frozenset({1, 2}), frozenset({1, 2}), Set[int])
|
||||
|
||||
def test_immutable_fields_annotated(self):
|
||||
"""Testing first appliance level, and Field types (annotated)"""
|
||||
|
||||
# class can be created
|
||||
class Appliance1(dm.BaseAppliance):
|
||||
StrVar: Annotated[str, dm.DABFieldInfo(doc="foo1")] = "default value"
|
||||
StrVar2: Annotated[str, dm.DABFieldInfo(doc="foo2")] = "default value2"
|
||||
VarInt: Annotated[int, dm.DABFieldInfo(doc="foo3")] = 12
|
||||
VarInt2: Annotated[int, dm.DABFieldInfo(doc="foo4")] = 21
|
||||
VarFloat: Annotated[float, dm.DABFieldInfo(doc="foo5")] = 12.1
|
||||
VarFloat2: Annotated[float, dm.DABFieldInfo(doc="foo6")] = 21.2
|
||||
VarComplex: Annotated[complex, dm.DABFieldInfo(doc="foo7")] = complex(3, 5)
|
||||
VarComplex2: Annotated[complex, dm.DABFieldInfo(doc="foo8")] = complex(8, 6)
|
||||
VarBool: Annotated[bool, dm.DABFieldInfo(doc="foo9")] = True
|
||||
VarBool2: Annotated[bool, dm.DABFieldInfo(doc="foo10")] = False
|
||||
VarBytes: Annotated[bytes, dm.DABFieldInfo(doc="foo11")] = bytes.fromhex("2Ef0 F1f2 ")
|
||||
VarBytes2: Annotated[bytes, dm.DABFieldInfo(doc="foo12")] = bytes.fromhex("2ff0 F7f2 ")
|
||||
|
||||
app1 = Appliance1()
|
||||
|
||||
self.immutable_vars__test_field(app1, "StrVar", "default value", "test")
|
||||
self.assertEqual(app1.__DABSchema__["StrVar"].doc, "foo1")
|
||||
self.immutable_vars__test_field(app1, "StrVar2", "default value2", "test2")
|
||||
self.assertEqual(app1.__DABSchema__["StrVar2"].doc, "foo2")
|
||||
self.immutable_vars__test_field(app1, "VarInt", 12, 13)
|
||||
self.assertEqual(app1.__DABSchema__["VarInt"].doc, "foo3")
|
||||
self.immutable_vars__test_field(app1, "VarInt2", 21, 22)
|
||||
self.assertEqual(app1.__DABSchema__["VarInt2"].doc, "foo4")
|
||||
self.immutable_vars__test_field(app1, "VarFloat", 12.1, 32)
|
||||
self.assertEqual(app1.__DABSchema__["VarFloat"].doc, "foo5")
|
||||
self.immutable_vars__test_field(app1, "VarFloat2", 21.2, 42)
|
||||
self.assertEqual(app1.__DABSchema__["VarFloat2"].doc, "foo6")
|
||||
self.immutable_vars__test_field(app1, "VarComplex", complex(3, 5), complex(1, 2))
|
||||
self.assertEqual(app1.__DABSchema__["VarComplex"].doc, "foo7")
|
||||
self.immutable_vars__test_field(app1, "VarComplex2", complex(8, 6), complex(3, 2))
|
||||
self.assertEqual(app1.__DABSchema__["VarComplex2"].doc, "foo8")
|
||||
self.immutable_vars__test_field(app1, "VarBool", True, False)
|
||||
self.assertEqual(app1.__DABSchema__["VarBool"].doc, "foo9")
|
||||
self.immutable_vars__test_field(app1, "VarBool2", False, True)
|
||||
self.assertEqual(app1.__DABSchema__["VarBool2"].doc, "foo10")
|
||||
self.immutable_vars__test_field(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("11f0 F1f2 "))
|
||||
self.assertEqual(app1.__DABSchema__["VarBytes"].doc, "foo11")
|
||||
self.immutable_vars__test_field(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("11f0 F1e2 "))
|
||||
self.assertEqual(app1.__DABSchema__["VarBytes2"].doc, "foo12")
|
||||
|
||||
with self.assertRaises(dm.InvalidFieldAnnotation):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: Annotated[str, "foo2"] = "default value2"
|
||||
|
||||
# annotation is parsed before the library can do anything, so the exception can only be TypeError
|
||||
with self.assertRaises(TypeError):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: Annotated[str] = "default value2"
|
||||
|
||||
def test_immutable_fields_inheritance(self):
|
||||
"""Testing first appliance level, and Field types (simple)"""
|
||||
|
||||
# class can be created
|
||||
class Appliance1(dm.BaseAppliance):
|
||||
StrVar: str = "default value"
|
||||
StrVar2: str = "default value2"
|
||||
VarInt: int = 12
|
||||
VarInt2: int = 21
|
||||
VarFloat: float = 12.1
|
||||
VarFloat2: float = 21.2
|
||||
VarComplex: complex = complex(3, 5)
|
||||
VarComplex2: complex = complex(8, 6)
|
||||
VarBool: bool = True
|
||||
VarBool2: bool = False
|
||||
VarBytes: bytes = bytes.fromhex("2Ef0 F1f2 ")
|
||||
VarBytes2: bytes = bytes.fromhex("2ff0 F7f2 ")
|
||||
|
||||
class Appliance2(Appliance1):
|
||||
StrVar = "moded value"
|
||||
StrVar2 = "moded value2"
|
||||
VarInt = 54
|
||||
VarInt2 = 23
|
||||
VarFloat = 2.6
|
||||
VarFloat2 = 1.5
|
||||
VarComplex = complex(7, 1)
|
||||
VarComplex2 = complex(3, 0)
|
||||
VarBool = False
|
||||
VarBool2 = True
|
||||
VarBytes = bytes.fromhex("21f0 e1f2 ")
|
||||
VarBytes2 = bytes.fromhex("2df0 F672 ")
|
||||
|
||||
app1 = Appliance1()
|
||||
|
||||
self.immutable_vars__test_field(app1, "StrVar", "default value", "test")
|
||||
self.immutable_vars__test_field(app1, "StrVar2", "default value2", "test2")
|
||||
self.immutable_vars__test_field(app1, "VarInt", 12, 13)
|
||||
self.immutable_vars__test_field(app1, "VarInt2", 21, 22)
|
||||
self.immutable_vars__test_field(app1, "VarFloat", 12.1, 32)
|
||||
self.immutable_vars__test_field(app1, "VarFloat2", 21.2, 42)
|
||||
self.immutable_vars__test_field(app1, "VarComplex", complex(3, 5), complex(1, 2))
|
||||
self.immutable_vars__test_field(app1, "VarComplex2", complex(8, 6), complex(3, 2))
|
||||
self.immutable_vars__test_field(app1, "VarBool", True, False)
|
||||
self.immutable_vars__test_field(app1, "VarBool2", False, True)
|
||||
self.immutable_vars__test_field(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("11f0 F1f2 "))
|
||||
self.immutable_vars__test_field(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("11f0 F1e2 "))
|
||||
|
||||
self.check_immutable_fields_schema(app1, "StrVar", "default value", "default value", str)
|
||||
self.check_immutable_fields_schema(app1, "StrVar2", "default value2", "default value2", str)
|
||||
self.check_immutable_fields_schema(app1, "VarInt", 12, 12, int)
|
||||
self.check_immutable_fields_schema(app1, "VarInt2", 21, 21, int)
|
||||
self.check_immutable_fields_schema(app1, "VarFloat", 12.1, 12.1, float)
|
||||
self.check_immutable_fields_schema(app1, "VarFloat2", 21.2, 21.2, float)
|
||||
self.check_immutable_fields_schema(app1, "VarComplex", complex(3, 5), complex(3, 5), complex)
|
||||
self.check_immutable_fields_schema(app1, "VarComplex2", complex(8, 6), complex(8, 6), complex)
|
||||
self.check_immutable_fields_schema(app1, "VarBool", True, True, bool)
|
||||
self.check_immutable_fields_schema(app1, "VarBool2", False, False, bool)
|
||||
self.check_immutable_fields_schema(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes)
|
||||
self.check_immutable_fields_schema(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("2ff0 F7f2 "), bytes)
|
||||
|
||||
app2 = Appliance2()
|
||||
|
||||
self.immutable_vars__test_field(app2, "StrVar", "moded value", "test")
|
||||
self.immutable_vars__test_field(app2, "StrVar2", "moded value2", "test2")
|
||||
self.immutable_vars__test_field(app2, "VarInt", 54, 13)
|
||||
self.immutable_vars__test_field(app2, "VarInt2", 23, 22)
|
||||
self.immutable_vars__test_field(app2, "VarFloat", 2.6, 32)
|
||||
self.immutable_vars__test_field(app2, "VarFloat2", 1.5, 42)
|
||||
self.immutable_vars__test_field(app2, "VarComplex", complex(7, 1), complex(1, 2))
|
||||
self.immutable_vars__test_field(app2, "VarComplex2", complex(3, 0), complex(3, 2))
|
||||
self.immutable_vars__test_field(app2, "VarBool", False, False)
|
||||
self.immutable_vars__test_field(app2, "VarBool2", True, True)
|
||||
self.immutable_vars__test_field(app2, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("11f0 F1f2 "))
|
||||
self.immutable_vars__test_field(app2, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("11f0 F1e2 "))
|
||||
|
||||
self.check_immutable_fields_schema(app2, "StrVar", "moded value", "default value", str)
|
||||
self.check_immutable_fields_schema(app2, "StrVar2", "moded value2", "default value2", str)
|
||||
self.check_immutable_fields_schema(app2, "VarInt", 54, 12, int)
|
||||
self.check_immutable_fields_schema(app2, "VarInt2", 23, 21, int)
|
||||
self.check_immutable_fields_schema(app2, "VarFloat", 2.6, 12.1, float)
|
||||
self.check_immutable_fields_schema(app2, "VarFloat2", 1.5, 21.2, float)
|
||||
self.check_immutable_fields_schema(app2, "VarComplex", complex(7, 1), complex(3, 5), complex)
|
||||
self.check_immutable_fields_schema(app2, "VarComplex2", complex(3, 0), complex(8, 6), complex)
|
||||
self.check_immutable_fields_schema(app2, "VarBool", False, True, bool)
|
||||
self.check_immutable_fields_schema(app2, "VarBool2", True, False, bool)
|
||||
self.check_immutable_fields_schema(app2, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes)
|
||||
self.check_immutable_fields_schema(app2, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("2ff0 F7f2 "), bytes)
|
||||
|
||||
class Appliance3(Appliance2):
|
||||
NewValue: str = "newval"
|
||||
|
||||
self.assertNotIn("NewValue", Appliance2.__DABSchema__)
|
||||
self.assertNotIn("NewValue", app2.__DABSchema__)
|
||||
self.assertNotIn("NewValue", Appliance1.__DABSchema__)
|
||||
self.assertNotIn("NewValue", app1.__DABSchema__)
|
||||
|
||||
app3 = Appliance3()
|
||||
self.immutable_vars__test_field(app3, "StrVar", "moded value", "test")
|
||||
self.immutable_vars__test_field(app3, "StrVar2", "moded value2", "test2")
|
||||
self.immutable_vars__test_field(app3, "VarInt", 54, 13)
|
||||
self.immutable_vars__test_field(app3, "VarInt2", 23, 22)
|
||||
self.immutable_vars__test_field(app3, "VarFloat", 2.6, 32)
|
||||
self.immutable_vars__test_field(app3, "VarFloat2", 1.5, 42)
|
||||
self.immutable_vars__test_field(app3, "VarComplex", complex(7, 1), complex(1, 2))
|
||||
self.immutable_vars__test_field(app3, "VarComplex2", complex(3, 0), complex(3, 2))
|
||||
self.immutable_vars__test_field(app3, "VarBool", False, False)
|
||||
self.immutable_vars__test_field(app3, "VarBool2", True, True)
|
||||
self.immutable_vars__test_field(app3, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("11f0 F1f2 "))
|
||||
self.immutable_vars__test_field(app3, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("11f0 F1e2 "))
|
||||
self.immutable_vars__test_field(app3, "NewValue", "newval", "test")
|
||||
|
||||
self.check_immutable_fields_schema(app3, "StrVar", "moded value", "default value", str)
|
||||
self.check_immutable_fields_schema(app3, "StrVar2", "moded value2", "default value2", str)
|
||||
self.check_immutable_fields_schema(app3, "VarInt", 54, 12, int)
|
||||
self.check_immutable_fields_schema(app3, "VarInt2", 23, 21, int)
|
||||
self.check_immutable_fields_schema(app3, "VarFloat", 2.6, 12.1, float)
|
||||
self.check_immutable_fields_schema(app3, "VarFloat2", 1.5, 21.2, float)
|
||||
self.check_immutable_fields_schema(app3, "VarComplex", complex(7, 1), complex(3, 5), complex)
|
||||
self.check_immutable_fields_schema(app3, "VarComplex2", complex(3, 0), complex(8, 6), complex)
|
||||
self.check_immutable_fields_schema(app3, "VarBool", False, True, bool)
|
||||
self.check_immutable_fields_schema(app3, "VarBool2", True, False, bool)
|
||||
self.check_immutable_fields_schema(app3, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes)
|
||||
self.check_immutable_fields_schema(app3, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("2ff0 F7f2 "), bytes)
|
||||
self.check_immutable_fields_schema(app3, "NewValue", "newval", "newval", str)
|
||||
|
||||
self.immutable_vars__test_field(app1, "StrVar", "default value", "test")
|
||||
self.immutable_vars__test_field(app1, "StrVar2", "default value2", "test2")
|
||||
self.immutable_vars__test_field(app1, "VarInt", 12, 13)
|
||||
self.immutable_vars__test_field(app1, "VarInt2", 21, 22)
|
||||
self.immutable_vars__test_field(app1, "VarFloat", 12.1, 32)
|
||||
self.immutable_vars__test_field(app1, "VarFloat2", 21.2, 42)
|
||||
self.immutable_vars__test_field(app1, "VarComplex", complex(3, 5), complex(1, 2))
|
||||
self.immutable_vars__test_field(app1, "VarComplex2", complex(8, 6), complex(3, 2))
|
||||
self.immutable_vars__test_field(app1, "VarBool", True, False)
|
||||
self.immutable_vars__test_field(app1, "VarBool2", False, True)
|
||||
self.immutable_vars__test_field(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("11f0 F1f2 "))
|
||||
self.immutable_vars__test_field(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("11f0 F1e2 "))
|
||||
|
||||
self.check_immutable_fields_schema(app1, "StrVar", "default value", "default value", str)
|
||||
self.check_immutable_fields_schema(app1, "StrVar2", "default value2", "default value2", str)
|
||||
self.check_immutable_fields_schema(app1, "VarInt", 12, 12, int)
|
||||
self.check_immutable_fields_schema(app1, "VarInt2", 21, 21, int)
|
||||
self.check_immutable_fields_schema(app1, "VarFloat", 12.1, 12.1, float)
|
||||
self.check_immutable_fields_schema(app1, "VarFloat2", 21.2, 21.2, float)
|
||||
self.check_immutable_fields_schema(app1, "VarComplex", complex(3, 5), complex(3, 5), complex)
|
||||
self.check_immutable_fields_schema(app1, "VarComplex2", complex(8, 6), complex(8, 6), complex)
|
||||
self.check_immutable_fields_schema(app1, "VarBool", True, True, bool)
|
||||
self.check_immutable_fields_schema(app1, "VarBool2", False, False, bool)
|
||||
self.check_immutable_fields_schema(app1, "VarBytes", bytes.fromhex("2Ef0 F1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes)
|
||||
self.check_immutable_fields_schema(app1, "VarBytes2", bytes.fromhex("2ff0 F7f2 "), bytes.fromhex("2ff0 F7f2 "), bytes)
|
||||
|
||||
self.immutable_vars__test_field(app2, "StrVar", "moded value", "test")
|
||||
self.immutable_vars__test_field(app2, "StrVar2", "moded value2", "test2")
|
||||
self.immutable_vars__test_field(app2, "VarInt", 54, 13)
|
||||
self.immutable_vars__test_field(app2, "VarInt2", 23, 22)
|
||||
self.immutable_vars__test_field(app2, "VarFloat", 2.6, 32)
|
||||
self.immutable_vars__test_field(app2, "VarFloat2", 1.5, 42)
|
||||
self.immutable_vars__test_field(app2, "VarComplex", complex(7, 1), complex(1, 2))
|
||||
self.immutable_vars__test_field(app2, "VarComplex2", complex(3, 0), complex(3, 2))
|
||||
self.immutable_vars__test_field(app2, "VarBool", False, False)
|
||||
self.immutable_vars__test_field(app2, "VarBool2", True, True)
|
||||
self.immutable_vars__test_field(app2, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("11f0 F1f2 "))
|
||||
self.immutable_vars__test_field(app2, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("11f0 F1e2 "))
|
||||
|
||||
self.check_immutable_fields_schema(app2, "StrVar", "moded value", "default value", str)
|
||||
self.check_immutable_fields_schema(app2, "StrVar2", "moded value2", "default value2", str)
|
||||
self.check_immutable_fields_schema(app2, "VarInt", 54, 12, int)
|
||||
self.check_immutable_fields_schema(app2, "VarInt2", 23, 21, int)
|
||||
self.check_immutable_fields_schema(app2, "VarFloat", 2.6, 12.1, float)
|
||||
self.check_immutable_fields_schema(app2, "VarFloat2", 1.5, 21.2, float)
|
||||
self.check_immutable_fields_schema(app2, "VarComplex", complex(7, 1), complex(3, 5), complex)
|
||||
self.check_immutable_fields_schema(app2, "VarComplex2", complex(3, 0), complex(8, 6), complex)
|
||||
self.check_immutable_fields_schema(app2, "VarBool", False, True, bool)
|
||||
self.check_immutable_fields_schema(app2, "VarBool2", True, False, bool)
|
||||
self.check_immutable_fields_schema(app2, "VarBytes", bytes.fromhex("21f0 e1f2 "), bytes.fromhex("2Ef0 F1f2 "), bytes)
|
||||
self.check_immutable_fields_schema(app2, "VarBytes2", bytes.fromhex("2df0 F672 "), bytes.fromhex("2ff0 F7f2 "), bytes)
|
||||
|
||||
with self.assertRaises(dm.ReadOnlyFieldAnnotation):
|
||||
|
||||
class _(Appliance1):
|
||||
StrVar: int = 12
|
||||
|
||||
with self.assertRaises(dm.ReadOnlyFieldAnnotation):
|
||||
|
||||
class _(Appliance3):
|
||||
NewValue: int = 12
|
||||
|
||||
with self.assertRaises(dm.ReadOnlyFieldAnnotation):
|
||||
|
||||
class _(Appliance3):
|
||||
StrVar: int = 12
|
||||
|
||||
def test_containers_field_inheritance(self):
|
||||
"""Testing first appliance level, and Field types (annotated)"""
|
||||
|
||||
# class can be created
|
||||
class Appliance1(dm.BaseAppliance):
|
||||
ListStr: list[str] = ["val1", "val2"]
|
||||
Dict1: dict[int, float] = {1: 1.1, 4: 7.6, 91: 23.6}
|
||||
Tuple1: "tuple[str,...]" = ("a", "c")
|
||||
FrozenSet1: frozenset[int] = frozenset({1, 2})
|
||||
Set1: set[int] = set({1, 2})
|
||||
|
||||
# class can be created
|
||||
class Appliance2(Appliance1):
|
||||
ListStr = ["mod val1", "mod val2"]
|
||||
Dict1 = {4: 1.1, 9: 7.6, 51: 23.6}
|
||||
Tuple1 = ("aa", "cc")
|
||||
FrozenSet1 = frozenset({14, 27})
|
||||
Set1 = set({1, 20})
|
||||
|
||||
app1 = Appliance1()
|
||||
|
||||
self.immutable_vars__test_field(app1, "ListStr", ("val1", "val2"), ["val2", "val3"])
|
||||
self.immutable_vars__test_field(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6})
|
||||
self.immutable_vars__test_field(app1, "Tuple1", ("a", "c"), ("h", "r"))
|
||||
self.immutable_vars__test_field(app1, "FrozenSet1", frozenset({1, 2}), frozenset({4, 0}))
|
||||
self.immutable_vars__test_field(app1, "Set1", frozenset({1, 2}), set({4, 0}))
|
||||
|
||||
self.check_immutable_fields_schema(app1, "ListStr", ("val1", "val2"), ("val1", "val2"), list[str])
|
||||
self.check_immutable_fields_schema(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, dict[int, float])
|
||||
self.check_immutable_fields_schema(app1, "Tuple1", ("a", "c"), ("a", "c"), tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), frozenset[int])
|
||||
self.check_immutable_fields_schema(app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), set[int])
|
||||
|
||||
app2 = Appliance2()
|
||||
|
||||
self.immutable_vars__test_field(app2, "ListStr", ("mod val1", "mod val2"), ["val2", "val3"])
|
||||
self.immutable_vars__test_field(app2, "Dict1", {4: 1.1, 9: 7.6, 51: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6})
|
||||
self.immutable_vars__test_field(app2, "Tuple1", ("aa", "cc"), ("h", "r"))
|
||||
self.immutable_vars__test_field(app2, "FrozenSet1", frozenset({14, 27}), frozenset({4, 0}))
|
||||
self.immutable_vars__test_field(app2, "Set1", frozenset({1, 20}), set({4, 0}))
|
||||
|
||||
self.check_immutable_fields_schema(app2, "ListStr", ("mod val1", "mod val2"), ("val1", "val2"), list[str])
|
||||
self.check_immutable_fields_schema(app2, "Dict1", {4: 1.1, 9: 7.6, 51: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, dict[int, float])
|
||||
self.check_immutable_fields_schema(app2, "Tuple1", ("aa", "cc"), ("a", "c"), tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app2, "FrozenSet1", frozenset({14, 27}), frozenset({1, 2}), frozenset[int])
|
||||
self.check_immutable_fields_schema(app2, "Set1", frozenset({1, 20}), frozenset({1, 2}), set[int])
|
||||
|
||||
self.immutable_vars__test_field(app1, "ListStr", ("val1", "val2"), ["val2", "val3"])
|
||||
self.immutable_vars__test_field(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6})
|
||||
self.immutable_vars__test_field(app1, "Tuple1", ("a", "c"), ("h", "r"))
|
||||
self.immutable_vars__test_field(app1, "FrozenSet1", frozenset({1, 2}), frozenset({4, 0}))
|
||||
self.immutable_vars__test_field(app1, "Set1", frozenset({1, 2}), set({4, 0}))
|
||||
|
||||
self.check_immutable_fields_schema(app1, "ListStr", ("val1", "val2"), ("val1", "val2"), list[str])
|
||||
self.check_immutable_fields_schema(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, dict[int, float])
|
||||
self.check_immutable_fields_schema(app1, "Tuple1", ("a", "c"), ("a", "c"), tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), frozenset[int])
|
||||
self.check_immutable_fields_schema(app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), set[int])
|
||||
|
||||
# class can be created
|
||||
class Appliance3(Appliance2):
|
||||
ListStr2: list[str] = ["mod val3", "mod val3"]
|
||||
Dict2: dict[int, float] = {9: 8.1, 5: 98.6, 551: 3.6}
|
||||
Tuple2: "tuple[str,...]" = ("aaa", "ccc")
|
||||
FrozenSet2: frozenset[int] = frozenset({114, 127})
|
||||
Set2: set[int] = set({10, 250})
|
||||
|
||||
app3 = Appliance3()
|
||||
|
||||
self.immutable_vars__test_field(app3, "ListStr", ("mod val1", "mod val2"), ["val2", "val3"])
|
||||
self.immutable_vars__test_field(app3, "Dict1", {4: 1.1, 9: 7.6, 51: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6})
|
||||
self.immutable_vars__test_field(app3, "Tuple1", ("aa", "cc"), ("h", "r"))
|
||||
self.immutable_vars__test_field(app3, "FrozenSet1", frozenset({14, 27}), frozenset({4, 0}))
|
||||
self.immutable_vars__test_field(app3, "Set1", frozenset({1, 20}), set({4, 0}))
|
||||
|
||||
self.immutable_vars__test_field(app3, "ListStr2", ("mod val3", "mod val3"), ["mod val3", "mod val3"])
|
||||
self.immutable_vars__test_field(app3, "Dict2", {9: 8.1, 5: 98.6, 551: 3.6}, {9: 8.1, 5: 98.6, 551: 3.6})
|
||||
self.immutable_vars__test_field(app3, "Tuple2", ("aaa", "ccc"), ("aaa", "ccc"))
|
||||
self.immutable_vars__test_field(app3, "FrozenSet2", frozenset({114, 127}), frozenset({114, 127}))
|
||||
self.immutable_vars__test_field(app3, "Set2", frozenset({10, 250}), set({10, 250}))
|
||||
|
||||
self.check_immutable_fields_schema(app3, "ListStr", ("mod val1", "mod val2"), ("val1", "val2"), list[str])
|
||||
self.check_immutable_fields_schema(app3, "Dict1", {4: 1.1, 9: 7.6, 51: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, dict[int, float])
|
||||
self.check_immutable_fields_schema(app3, "Tuple1", ("aa", "cc"), ("a", "c"), tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app3, "FrozenSet1", frozenset({14, 27}), frozenset({1, 2}), frozenset[int])
|
||||
self.check_immutable_fields_schema(app3, "Set1", frozenset({1, 20}), frozenset({1, 2}), set[int])
|
||||
|
||||
self.check_immutable_fields_schema(app3, "ListStr2", ("mod val3", "mod val3"), ("mod val3", "mod val3"), list[str])
|
||||
self.check_immutable_fields_schema(app3, "Dict2", {9: 8.1, 5: 98.6, 551: 3.6}, {9: 8.1, 5: 98.6, 551: 3.6}, dict[int, float])
|
||||
self.check_immutable_fields_schema(app3, "Tuple2", ("aaa", "ccc"), ("aaa", "ccc"), tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app3, "FrozenSet2", frozenset({114, 127}), frozenset({114, 127}), frozenset[int])
|
||||
self.check_immutable_fields_schema(app3, "Set2", frozenset({10, 250}), frozenset({10, 250}), set[int])
|
||||
|
||||
self.immutable_vars__test_field(app2, "ListStr", ("mod val1", "mod val2"), ["val2", "val3"])
|
||||
self.immutable_vars__test_field(app2, "Dict1", {4: 1.1, 9: 7.6, 51: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6})
|
||||
self.immutable_vars__test_field(app2, "Tuple1", ("aa", "cc"), ("h", "r"))
|
||||
self.immutable_vars__test_field(app2, "FrozenSet1", frozenset({14, 27}), frozenset({4, 0}))
|
||||
self.immutable_vars__test_field(app2, "Set1", frozenset({1, 20}), set({4, 0}))
|
||||
|
||||
self.check_immutable_fields_schema(app2, "ListStr", ("mod val1", "mod val2"), ("val1", "val2"), list[str])
|
||||
self.check_immutable_fields_schema(app2, "Dict1", {4: 1.1, 9: 7.6, 51: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, dict[int, float])
|
||||
self.check_immutable_fields_schema(app2, "Tuple1", ("aa", "cc"), ("a", "c"), tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app2, "FrozenSet1", frozenset({14, 27}), frozenset({1, 2}), frozenset[int])
|
||||
self.check_immutable_fields_schema(app2, "Set1", frozenset({1, 20}), frozenset({1, 2}), set[int])
|
||||
|
||||
self.immutable_vars__test_field(app1, "ListStr", ("val1", "val2"), ["val2", "val3"])
|
||||
self.immutable_vars__test_field(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6})
|
||||
self.immutable_vars__test_field(app1, "Tuple1", ("a", "c"), ("h", "r"))
|
||||
self.immutable_vars__test_field(app1, "FrozenSet1", frozenset({1, 2}), frozenset({4, 0}))
|
||||
self.immutable_vars__test_field(app1, "Set1", frozenset({1, 2}), set({4, 0}))
|
||||
|
||||
self.check_immutable_fields_schema(app1, "ListStr", ("val1", "val2"), ("val1", "val2"), list[str])
|
||||
self.check_immutable_fields_schema(app1, "Dict1", {1: 1.1, 4: 7.6, 91: 23.6}, {1: 1.1, 4: 7.6, 91: 23.6}, dict[int, float])
|
||||
self.check_immutable_fields_schema(app1, "Tuple1", ("a", "c"), ("a", "c"), tuple[str, ...])
|
||||
self.check_immutable_fields_schema(app1, "FrozenSet1", frozenset({1, 2}), frozenset({1, 2}), frozenset[int])
|
||||
self.check_immutable_fields_schema(app1, "Set1", frozenset({1, 2}), frozenset({1, 2}), set[int])
|
||||
|
||||
def test_initializer(self):
|
||||
"""Testing first appliance level, and Field types (simple)"""
|
||||
|
||||
# class can be created
|
||||
class Appliance1(dm.BaseAppliance):
|
||||
VarInt: int = 12
|
||||
VarInt2: int = 21
|
||||
list1: list[int] = [1]
|
||||
set1: set[float] = {1.43}
|
||||
set2: set[float] = {5.43}
|
||||
VarStr1: str = "abcd"
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
|
||||
cls.VarInt2 = cls.VarInt + 1
|
||||
cls.list1.append(2)
|
||||
cls.set2 = cls.set1 | {1234}
|
||||
cls.set1 = {0}
|
||||
cls.VarStr1 = cls.VarStr1.upper()
|
||||
|
||||
app1 = Appliance1()
|
||||
|
||||
self.assertEqual(app1.VarInt, 12)
|
||||
self.assertEqual(app1.VarInt2, 13)
|
||||
self.assertEqual(app1.set1, frozenset({0}))
|
||||
self.assertEqual(app1.set2, frozenset((1.43, 1234)))
|
||||
self.assertEqual(app1.VarStr1, "ABCD")
|
||||
|
||||
# class can be created
|
||||
class _(dm.BaseAppliance):
|
||||
VarInt: int = 41
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
|
||||
cls.VarInt = cls.VarInt + 1
|
||||
|
||||
app2 = _()
|
||||
self.assertEqual(app2.VarInt, 42)
|
||||
|
||||
def test_initializer_safe(self):
|
||||
|
||||
with self.assertRaises(dm.ImportForbidden):
|
||||
|
||||
class test(dm.BaseAppliance):
|
||||
_: int = 0
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
import pprint
|
||||
|
||||
with self.assertRaises(NameError):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: int = 0
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
eval("2 ** 8")
|
||||
|
||||
with self.assertRaises(NameError):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: int = 0
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
open("foo")
|
||||
|
||||
with self.assertRaises(NameError):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: int = 0
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
exec("exit()")
|
||||
|
||||
with self.assertRaises(NameError):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: int = 0
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
compile("print(55)", "test", "eval")
|
||||
|
||||
with self.assertRaises(NameError):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: int = 0
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
compile("print(55)", "test", "eval")
|
||||
|
||||
with self.assertRaises(dm.FunctionForbidden):
|
||||
|
||||
def testfc():
|
||||
eval("print('test')")
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: int = 0
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
testfc()
|
||||
|
||||
# class can be created
|
||||
class Appliance2(dm.BaseAppliance):
|
||||
VarInt: int = -56
|
||||
VarInt2: int = 0
|
||||
VarInt3: int = 0
|
||||
VarInt4: int = 0
|
||||
VarInt5: int = 0
|
||||
VarFloat: float = 1.23
|
||||
list1: list[int] = [1, 2, 0, 4]
|
||||
list2: Optional[list[int]] = None
|
||||
list3: Optional[list[int]] = None
|
||||
set1: set[float] = {1.43}
|
||||
set2: set[float] = {5.43}
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
cls.VarInt2 = abs(cls.VarInt)
|
||||
cls.VarFloat = round(cls.VarFloat)
|
||||
cls.VarInt = min(cls.list1)
|
||||
cls.VarInt3 = max(cls.list1)
|
||||
cls.VarInt4 = sum(cls.list1)
|
||||
cls.VarInt5 = len(cls.list1)
|
||||
cls.list2 = sorted(cls.list1)
|
||||
cls.list3 = []
|
||||
for i in range(5):
|
||||
cls.list3.append(i)
|
||||
cls.set2 = {math.ceil(list(cls.set1)[0])}
|
||||
|
||||
app2 = Appliance2()
|
||||
self.assertEqual(app2.VarInt2, 56)
|
||||
self.assertEqual(app2.VarFloat, 1)
|
||||
self.assertEqual(app2.VarInt, 0)
|
||||
self.assertEqual(app2.VarInt3, 4)
|
||||
self.assertEqual(app2.VarInt4, 7)
|
||||
self.assertEqual(app2.VarInt5, 4)
|
||||
self.assertEqual(app2.list2, (0, 1, 2, 4))
|
||||
self.assertEqual(app2.list3, (0, 1, 2, 3, 4))
|
||||
self.assertEqual(app2.set2, {2})
|
||||
|
||||
with self.assertRaises(NameError):
|
||||
|
||||
class _(dm.BaseAppliance):
|
||||
_: int = 0
|
||||
|
||||
@classmethod
|
||||
def __initializer(cls):
|
||||
test_initializer_safe_testfc()
|
||||
|
||||
def test_feature(self):
|
||||
"""Testing first appliance level, and Field types (simple)"""
|
||||
|
||||
|
||||
|
||||
# class can be created
|
||||
class Appliance1(dm.BaseAppliance):
|
||||
VarStrOuter: str = "testvalue APPLIANCE"
|
||||
class Feature1(dm.BaseFeature):
|
||||
VarStrInner: str = "testvalue FEATURE"
|
||||
app1 = Appliance1()
|
||||
|
||||
self.assertIsInstance(app1.__DABSchema__["VarStrOuter"],dm.FrozenDABField)
|
||||
self.assertTrue(hasattr(app1, "Feature1"))
|
||||
self.assertTrue(hasattr(app1.Feature1, "VarStrInner"))
|
||||
self.assertIn("Feature1",app1.__DABFeatures__)
|
||||
self.assertIn("VarStrInner",app1.__DABFeatures__["Feature1"].__DABSchema__)
|
||||
self.assertIsInstance(app1.__DABFeatures__["Feature1"].__DABSchema__["VarStrInner"],dm.FrozenDABField)
|
||||
# ---------- main ----------
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user