rithm 14.8.0

Arbitrary precision arithmetic.
Documentation
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
            )
        )
    ] == []