import importlib
import inspect
import sys
import typing
from types import ModuleType
from typing import Any, ClassVar, get_args, get_type_hints
import pytest
from _pytest.monkeypatch import MonkeyPatch
from typing_extensions import Self
from tests.meta_utils import (
do_signatures_have_same_api,
escape_if_built_in_name,
is_class_final,
is_descriptor,
is_private_object_name,
natural_sorting_key,
to_base_annotation,
to_class_signature,
to_lists_different_elements,
to_self_referential_class_fields,
to_sorted_lists_diff,
to_unsorted_list_elements,
)
@pytest.fixture(scope='module')
def module() -> ModuleType:
from rithm import enums
return enums
@pytest.fixture(scope='module')
def module_namespace(module: ModuleType) -> dict[str, Any]:
return vars(module)
@pytest.fixture
def type_annotated_module(
module: ModuleType, monkeypatch: MonkeyPatch
) -> ModuleType:
module_name = module.__name__
rest_modules = sys.modules.copy()
del rest_modules[module_name]
with monkeypatch.context() as patch:
patch.setattr(sys, 'modules', rest_modules)
patch.setattr(typing, 'TYPE_CHECKING', True)
return importlib.import_module(module_name)
@pytest.fixture
def type_annotated_module_namespace(
type_annotated_module: ModuleType,
) -> dict[str, Any]:
return vars(type_annotated_module)
def test_type_annotations(
module_namespace: dict[str, Any],
type_annotated_module_namespace: dict[str, Any],
) -> None:
assert type_annotated_module_namespace.keys() == module_namespace.keys()
assert [
name
for name in (
type_annotated_module_namespace.keys() & module_namespace.keys()
)
if (
type(type_annotated_module_namespace[name])
is not type(module_namespace[name])
)
and (
inspect.isfunction(type_annotated_module_namespace[name])
is not inspect.isbuiltin(module_namespace[name])
)
] == []
def test_member_module_names(
module: ModuleType, module_namespace: dict[str, Any]
) -> None:
assert {
name: value
for name, value in module_namespace.items()
if (
not is_private_object_name(name)
and (
inspect.isfunction(value)
or inspect.isbuiltin(value)
or inspect.isclass(value)
)
and getattr(value, '__module__', None) != module.__name__
)
} == {}
def test_enum_like_classes(
module_namespace: dict[str, Any],
type_annotated_module_namespace: dict[str, Any],
) -> None:
classes = {
name: value
for name, value in module_namespace.items()
if (
inspect.isclass(value)
and value.__module__ == module_namespace['__name__']
)
}
type_annotated_classes = {
name: value
for name, value in type_annotated_module_namespace.items()
if (
inspect.isclass(value)
and value.__module__ == type_annotated_module_namespace['__name__']
)
}
enum_like_classes_field_names = {
cls: field_names
for cls in classes.values()
if (
is_class_final(cls)
and (
len(field_names := list(to_self_referential_class_fields(cls)))
!= 0
)
)
}
type_annotated_enum_like_classes_field_names = {
cls: field_names
for cls in type_annotated_classes.values()
if (
is_class_final(cls)
and len(
field_names := [
field_name
for field_name, field_annotation in get_type_hints(
cls, type_annotated_module_namespace
).items()
if (
to_base_annotation(field_annotation) is ClassVar
and get_args(field_annotation)[0] is Self
)
]
)
!= 0
)
}
assert {cls.__qualname__ for cls in enum_like_classes_field_names} == {
cls.__qualname__
for cls in type_annotated_enum_like_classes_field_names
}
assert {
cls: unsorted_field_names
for cls, field_names in enum_like_classes_field_names.items()
if (
len(
unsorted_field_names := to_unsorted_list_elements(
field_names, key=natural_sorting_key
)
)
!= 0
)
} == {}
assert {
cls: unsorted_field_names
for cls, field_names in (
type_annotated_enum_like_classes_field_names.items()
)
if (
len(
unsorted_field_names := to_unsorted_list_elements(
field_names, key=natural_sorting_key
)
)
!= 0
)
} == {}
assert {
cls: to_sorted_lists_diff(field_names, type_annotated_field_names)
for cls, field_names in enum_like_classes_field_names.items()
if (
field_names
!= (
type_annotated_field_names := (
type_annotated_enum_like_classes_field_names[
type_annotated_classes[cls.__qualname__]
]
)
)
)
} == {}
def test_constructible_classes(
module_namespace: dict[str, Any],
type_annotated_module_namespace: dict[str, Any],
) -> None:
classes = {
name: value
for name, value in module_namespace.items()
if (
inspect.isclass(value)
and value.__module__ == module_namespace['__name__']
)
}
type_annotated_classes = {
name: value
for name, value in type_annotated_module_namespace.items()
if (
inspect.isclass(value)
and value.__module__ == type_annotated_module_namespace['__name__']
)
}
constructible_class_signatures = {
cls: signature
for cls in classes.values()
if (signature := to_class_signature(cls)) is not None
}
type_annotated_class_signatures = {
cls: signature
for cls in type_annotated_classes.values()
if (signature := to_class_signature(cls)) is not None
}
assert constructible_class_signatures
assert classes.keys() == type_annotated_classes.keys()
assert {cls.__qualname__ for cls in constructible_class_signatures} - {
cls.__qualname__ for cls in type_annotated_class_signatures
} == set()
assert [
class_name
for class_name in (
{cls.__qualname__ for cls in type_annotated_class_signatures}
- {cls.__qualname__ for cls in constructible_class_signatures}
)
if (
not is_class_final(module_namespace[class_name])
and not inspect.isabstract(
type_annotated_module_namespace[class_name]
)
)
] == []
assert [
cls
for cls in constructible_class_signatures
if (
is_class_final(cls)
is not is_class_final(
type_annotated_module_namespace[cls.__qualname__]
)
)
] == []
assert {
cls: to_sorted_lists_diff(
sorted_descriptor_names, sorted_parameter_names
)
for cls, signature in constructible_class_signatures.items()
if (
(
sorted_parameter_names := sorted(
parameter_name
for parameter_name in signature.parameters
if not is_private_object_name(parameter_name)
)
)
!= (
sorted_descriptor_names := sorted(
escape_if_built_in_name(field_name)
for field_name, field in vars(cls).items()
if is_descriptor(field)
)
)
)
} == {}
assert {
cls: (
type_annotated_cls
if type_annotated_cls is None
else to_lists_different_elements(parameter_names, descriptors)
)
for cls, signature in constructible_class_signatures.items()
if (
(
(
(
type_annotated_cls
:= type_annotated_module_namespace.get(
cls.__qualname__
)
)
is None
)
and (descriptors := [])
and (parameter_names := [])
)
or (
(
parameter_names := [
parameter_name
for parameter_name in signature.parameters
if not is_private_object_name(parameter_name)
]
)
!= (
descriptors := [
escape_if_built_in_name(field_name)
for field_name, field in vars(
type_annotated_cls
).items()
if isinstance(field, property)
]
)
)
)
} == {}
assert [
cls
for cls, signature in constructible_class_signatures.items()
if (
(
type_annotated_cls := type_annotated_module_namespace.get(
cls.__qualname__
)
)
is None
or (
(
type_annotated_signature := to_class_signature(
type_annotated_cls
)
)
is None
)
or not do_signatures_have_same_api(
signature, type_annotated_signature
)
)
] == []