1.0.1
This commit is contained in:
0
tests/local/__init__.py
Normal file
0
tests/local/__init__.py
Normal file
99
tests/local/test_class_naming.py
Normal file
99
tests/local/test_class_naming.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import dynamix_sdk.types as sdk_types
|
||||
from dynamix_sdk.utils import gen_cls_name_from_url_path
|
||||
|
||||
|
||||
def test_api_class_naming(api_groups):
|
||||
for api_group in api_groups:
|
||||
correct_class_name = f'{api_group.name.capitalize()}API'
|
||||
assert api_group.cls.__qualname__ == correct_class_name, (
|
||||
f'\nAPI group: {api_group.name}'
|
||||
f'\nCorrect API class name: {correct_class_name}'
|
||||
)
|
||||
|
||||
for api_subgroup in api_group.subgroups:
|
||||
correct_class_name = (
|
||||
f'{api_group.name.capitalize()}'
|
||||
f'{api_subgroup.name.capitalize()}'
|
||||
f'API'
|
||||
)
|
||||
assert api_subgroup.cls.__qualname__ == correct_class_name, (
|
||||
f'\nAPI group: {api_group.name}'
|
||||
f'\nAPI subgroup: {api_subgroup.name}'
|
||||
f'\nCorrect API class name: {correct_class_name}'
|
||||
)
|
||||
|
||||
|
||||
def test_protocol_class_naming(sdk_dx_functions):
|
||||
for sdk_func in sdk_dx_functions:
|
||||
correct_class_name = gen_cls_name_from_url_path(
|
||||
url_path=sdk_func.url_path,
|
||||
postfix='Protocol',
|
||||
)
|
||||
|
||||
assert sdk_func.proto_cls.__qualname__ == correct_class_name, (
|
||||
f'\nFunction call attributes: {sdk_func.call_attrs}'
|
||||
f'\nURL path: {sdk_func.url_path}'
|
||||
f'\nCorrect Protocol class name: {correct_class_name}'
|
||||
)
|
||||
|
||||
|
||||
def get_subclasses(cls: type, result: list[type] | None = None):
|
||||
if result:
|
||||
_result = result
|
||||
else:
|
||||
_result: list[type] = []
|
||||
|
||||
for subclass in cls.__subclasses__():
|
||||
if issubclass(subclass.__bases__[0], cls):
|
||||
_result.append(subclass)
|
||||
_result += get_subclasses(subclass)
|
||||
|
||||
return _result
|
||||
|
||||
|
||||
def test_params_nested_model_class_naming():
|
||||
base_cls = sdk_types.BaseAPIParamsNestedModel
|
||||
for params_nm_cls in get_subclasses(base_cls):
|
||||
suffix = 'APIParamsNM'
|
||||
assert params_nm_cls.__qualname__.endswith(suffix), (
|
||||
f'Class {params_nm_cls.__qualname__}:'
|
||||
f' all subclasses of {base_cls.__qualname__}'
|
||||
f' must have a name with suffix "{suffix}".'
|
||||
)
|
||||
|
||||
|
||||
def test_result_class_naming(sdk_dx_functions):
|
||||
for sdk_func in sdk_dx_functions:
|
||||
if issubclass(sdk_func.result_cls, sdk_types.BaseAPIResultModel):
|
||||
result_cls_postfix = 'ResultModel'
|
||||
elif issubclass(sdk_func.result_cls, sdk_types.BaseAPIResultStr):
|
||||
result_cls_postfix = 'ResultStr'
|
||||
elif issubclass(sdk_func.result_cls, sdk_types.BaseAPIResultInt):
|
||||
result_cls_postfix = 'ResultInt'
|
||||
elif issubclass(sdk_func.result_cls, sdk_types.BaseAPIResultBool):
|
||||
result_cls_postfix = 'ResultBool'
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
result_cls_name = gen_cls_name_from_url_path(
|
||||
url_path=sdk_func.url_path,
|
||||
postfix=result_cls_postfix,
|
||||
)
|
||||
assert sdk_func.result_cls.__qualname__ == result_cls_name, (
|
||||
f'\nFunction call attributes: {sdk_func.call_attrs}'
|
||||
f'\nURL path: {sdk_func.url_path}'
|
||||
f'\nResult base class:'
|
||||
f' {sdk_func.result_cls.__bases__[0].__qualname__}'
|
||||
f'\nCorrect result class name: {result_cls_name}'
|
||||
)
|
||||
|
||||
|
||||
def test_result_nested_model_class_naming():
|
||||
base_cls = sdk_types.BaseAPIResultNestedModel
|
||||
for result_nm_cls in get_subclasses(base_cls):
|
||||
suffix = 'APIResultNM'
|
||||
assert result_nm_cls.__qualname__.endswith(suffix), (
|
||||
f'Class {result_nm_cls.__qualname__}:'
|
||||
f' all subclasses of {base_cls.__qualname__}'
|
||||
f' must have a name with suffix "{suffix}".'
|
||||
)
|
||||
68
tests/local/test_name_mapping_file.py
Normal file
68
tests/local/test_name_mapping_file.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from dynamix_sdk import types as sdk_types
|
||||
from dynamix_sdk.base import (
|
||||
get_alias,
|
||||
name_mapping_dict,
|
||||
)
|
||||
|
||||
|
||||
def test_missing_mappings_in_name_mapping_file(dx_models):
|
||||
attrs_without_mapping = []
|
||||
for model_cls in dx_models:
|
||||
for field_name in model_cls.model_fields.keys():
|
||||
try:
|
||||
get_alias(
|
||||
field_name=field_name,
|
||||
model_cls=model_cls,
|
||||
name_mapping_dict=name_mapping_dict
|
||||
)
|
||||
except KeyError:
|
||||
attrs_without_mapping.append(
|
||||
f'{model_cls.__qualname__}.{field_name}'
|
||||
)
|
||||
|
||||
attrs_without_mapping.sort()
|
||||
|
||||
assert not attrs_without_mapping, (
|
||||
f'{len(attrs_without_mapping)} attributes without mapping:'
|
||||
f' {attrs_without_mapping}'
|
||||
)
|
||||
|
||||
|
||||
def test_unused_mappings_in_name_mapping_file(dx_models):
|
||||
mapping_dict_keys = set(name_mapping_dict.keys())
|
||||
|
||||
def exclude_used_keys(
|
||||
model_cls: type[sdk_types.BaseModel],
|
||||
mapping_dict_keys: set[str],
|
||||
):
|
||||
for field_name in model_cls.__annotations__.keys():
|
||||
used_key = None
|
||||
|
||||
individual_alias_key = f'{field_name}__{model_cls.__qualname__}'
|
||||
if individual_alias_key in mapping_dict_keys:
|
||||
used_key = individual_alias_key
|
||||
elif field_name in mapping_dict_keys:
|
||||
used_key = field_name
|
||||
|
||||
if used_key and used_key in mapping_dict_keys:
|
||||
mapping_dict_keys.remove(used_key)
|
||||
|
||||
for base_cls in model_cls.__bases__:
|
||||
if issubclass(base_cls, sdk_types.BaseModel):
|
||||
exclude_used_keys(
|
||||
model_cls=base_cls,
|
||||
mapping_dict_keys=mapping_dict_keys,
|
||||
)
|
||||
|
||||
for model_cls in dx_models:
|
||||
exclude_used_keys(
|
||||
model_cls=model_cls,
|
||||
mapping_dict_keys=mapping_dict_keys,
|
||||
)
|
||||
|
||||
unused_mapping_dict_keys = sorted(mapping_dict_keys)
|
||||
|
||||
assert not unused_mapping_dict_keys, (
|
||||
f'{len(unused_mapping_dict_keys)} unused keys in mapping file:'
|
||||
f' {unused_mapping_dict_keys}.'
|
||||
)
|
||||
154
tests/local/test_types.py
Normal file
154
tests/local/test_types.py
Normal file
@@ -0,0 +1,154 @@
|
||||
from enum import Enum
|
||||
import inspect
|
||||
from types import GenericAlias, ModuleType, UnionType
|
||||
from typing import Any, get_args
|
||||
|
||||
import dynamix_sdk.types as sdk_types
|
||||
import dynamix_sdk.api._nested.params as nested_params
|
||||
import dynamix_sdk.api._nested.result as nested_result
|
||||
import dynamix_sdk.api._nested.enums as nested_enums
|
||||
|
||||
from tests.conftest import SDKFunction
|
||||
|
||||
|
||||
def check_model_field_annotation(
|
||||
annotation: Any,
|
||||
field_descr: str,
|
||||
valid_nested_model_cls: type[sdk_types.BaseModel],
|
||||
):
|
||||
if not annotation:
|
||||
raise TypeError
|
||||
|
||||
if annotation is Any:
|
||||
return
|
||||
|
||||
assert annotation is not list, (
|
||||
f'{field_descr}: missing list elements type annotation.'
|
||||
)
|
||||
|
||||
if isinstance(annotation, (UnionType, GenericAlias)):
|
||||
for annotation in get_args(annotation):
|
||||
check_model_field_annotation(
|
||||
annotation=annotation,
|
||||
field_descr=field_descr,
|
||||
valid_nested_model_cls=valid_nested_model_cls,
|
||||
)
|
||||
elif issubclass(annotation, sdk_types.BaseModel):
|
||||
model_cls = annotation
|
||||
assert issubclass(model_cls, valid_nested_model_cls), (
|
||||
f'{field_descr}: nested model class must be'
|
||||
f' a subclass of {valid_nested_model_cls.__qualname__}.'
|
||||
)
|
||||
|
||||
check_model_field_annotations(
|
||||
model_cls=model_cls,
|
||||
valid_nested_model_cls=valid_nested_model_cls,
|
||||
)
|
||||
|
||||
|
||||
def check_model_field_annotations(
|
||||
model_cls: type[sdk_types.BaseModel],
|
||||
valid_nested_model_cls: type[sdk_types.BaseModel],
|
||||
):
|
||||
for field_name, field_info in model_cls.model_fields.items():
|
||||
field_descr = f'{model_cls.__qualname__}.{field_name}'
|
||||
|
||||
check_model_field_annotation(
|
||||
annotation=field_info.annotation,
|
||||
field_descr=field_descr,
|
||||
valid_nested_model_cls=valid_nested_model_cls,
|
||||
)
|
||||
|
||||
|
||||
def test_params_model_fields_type(sdk_dx_functions: tuple[SDKFunction, ...]):
|
||||
for sdk_func in sdk_dx_functions:
|
||||
params = inspect.signature(sdk_func.proto_method).parameters
|
||||
for param_name, param in params.items():
|
||||
if param_name == 'self':
|
||||
continue
|
||||
|
||||
field_descr = (
|
||||
f'{sdk_func.proto_cls.__qualname__}.'
|
||||
f'{sdk_func.call_attrs[-1]}.'
|
||||
f'{param_name}'
|
||||
)
|
||||
|
||||
assert param.annotation is not inspect.Parameter.empty, (
|
||||
f'{field_descr}: missing type annotation.'
|
||||
)
|
||||
|
||||
assert param.kind == param.KEYWORD_ONLY, (
|
||||
f'Parameter {field_descr} must be keyword-only.'
|
||||
)
|
||||
|
||||
check_model_field_annotation(
|
||||
annotation=param.annotation,
|
||||
field_descr=field_descr,
|
||||
valid_nested_model_cls=sdk_types.BaseAPIParamsNestedModel,
|
||||
)
|
||||
|
||||
|
||||
def test_function_return_type(sdk_dx_functions):
|
||||
for sdk_func in sdk_dx_functions:
|
||||
assert issubclass(sdk_func.result_cls, sdk_types.BaseAPIResult), (
|
||||
f'Return type for method'
|
||||
f' {sdk_func.proto_cls.__qualname__}.{sdk_func.call_attrs[-1]}'
|
||||
f' must be a subclass of BaseAPIResult.'
|
||||
)
|
||||
|
||||
|
||||
def test_result_model_fields_type():
|
||||
valid_nested_model_cls = sdk_types.BaseAPIResultNestedModel
|
||||
|
||||
for subcls in sdk_types.BaseAPIResultModel.__subclasses__():
|
||||
check_model_field_annotations(
|
||||
model_cls=subcls,
|
||||
valid_nested_model_cls=valid_nested_model_cls,
|
||||
)
|
||||
for subcls in sdk_types.BaseAPIResultNestedModel.__subclasses__():
|
||||
check_model_field_annotations(
|
||||
model_cls=subcls,
|
||||
valid_nested_model_cls=valid_nested_model_cls,
|
||||
)
|
||||
|
||||
|
||||
def check_class_inheritance_in_module(
|
||||
module: ModuleType,
|
||||
valid_base_cls: type,
|
||||
):
|
||||
for attr_name in dir(module):
|
||||
if attr_name.startswith('_'):
|
||||
continue
|
||||
|
||||
attr = getattr(module, attr_name)
|
||||
|
||||
if not isinstance(attr, type):
|
||||
continue
|
||||
|
||||
nested_result_cls = attr
|
||||
|
||||
assert issubclass(nested_result_cls, valid_base_cls), (
|
||||
f'Class {nested_result_cls.__qualname__}:'
|
||||
f' must be a subclass of class {valid_base_cls.__qualname__}.'
|
||||
)
|
||||
|
||||
|
||||
def test_class_inheritance_in_nested_result_module():
|
||||
check_class_inheritance_in_module(
|
||||
module=nested_result,
|
||||
valid_base_cls=sdk_types.BaseAPIResultNestedModel,
|
||||
)
|
||||
|
||||
|
||||
def test_class_inheritance_in_nested_params_module():
|
||||
check_class_inheritance_in_module(
|
||||
module=nested_params,
|
||||
valid_base_cls=sdk_types.BaseAPIParamsNestedModel,
|
||||
)
|
||||
|
||||
|
||||
def test_class_inheritance_in_nested_enums_module():
|
||||
check_class_inheritance_in_module(
|
||||
module=nested_enums,
|
||||
valid_base_cls=Enum,
|
||||
)
|
||||
Reference in New Issue
Block a user