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,
    is_class_final,
    is_private_object_name,
    to_base_annotation,
    to_class_signature,
    to_self_referential_class_fields,
)


@pytest.fixture(scope='module')
def module() -> ModuleType:
    from rithm import integer

    return integer


@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 = [
        cls
        for cls in classes.values()
        if (
            is_class_final(cls)
            and len(list(to_self_referential_class_fields(cls))) != 0
        )
    ]
    type_annotated_enum_like_classes = [
        cls
        for cls in type_annotated_classes.values()
        if (
            is_class_final(cls)
            and len(
                [
                    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 enum_like_classes == []
    assert type_annotated_enum_like_classes == []


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
        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
            )
        )
    ] == []