rithm 14.6.0

Arbitrary precision arithmetic.
Documentation
from __future__ import annotations

import builtins
import inspect
import pickle
from collections.abc import Callable
from itertools import zip_longest
from platform import python_implementation
from typing import Annotated, Any, Protocol, TypeVar

from typing_extensions import Self, get_args, get_origin


def do_signatures_have_same_api(
    left: inspect.Signature, right: inspect.Signature
) -> bool:
    left_parameters, right_parameters = left.parameters, right.parameters
    return (
        all(
            (
                to_typeless_parameter(left_parameters[parameter_name])
                == to_typeless_parameter(right_parameters[parameter_name])
            )
            for parameter_name in (
                left_parameters.keys() & right_parameters.keys()
            )
        )
        and all(
            is_private_optional_parameter(left_parameters[parameter_name])
            for parameter_name in (
                left_parameters.keys() - right_parameters.keys()
            )
        )
        and all(
            is_private_optional_parameter(right_parameters[parameter_name])
            for parameter_name in (
                right_parameters.keys() - left_parameters.keys()
            )
        )
    )


def is_private_optional_parameter(parameter: inspect.Parameter) -> bool:
    return is_private_object_name(parameter.name) and (
        parameter.kind is inspect.Parameter.KEYWORD_ONLY
    )


def escape_if_built_in_name(name: str, /) -> str:
    return f'{name}_' if name in vars(builtins) else name


def is_class_final(cls: type[Any], /) -> bool:
    return getattr(cls, '__final__', False)


if python_implementation() == 'CPython':

    def is_descriptor(value: Any, /) -> bool:
        return inspect.isgetsetdescriptor(value)

else:

    def is_descriptor(value: Any, /) -> bool:
        return isinstance(value, property)


def is_private_object_name(value: str, /) -> bool:
    return value.startswith('_')


def natural_sorting_key(value: str, /) -> list[int | str]:
    result: list[int | str] = []
    part_index = 0
    while part_index < len(value):
        part_start_index = part_index
        is_integer_part = value[part_index].isdigit()
        while (part_index := part_index + 1) < len(value) and value[
            part_index
        ].isdigit() is is_integer_part:
            pass
        part = value[part_start_index:part_index]
        result.append(int(part) if is_integer_part else part)
    return result


def pickling_round_trip(value: Any, /) -> Any:
    return pickle.loads(pickle.dumps(value))


def to_base_annotation(annotation: Any, /) -> Any | None:
    result = get_origin(annotation)
    if result is Annotated:
        result = get_origin(get_args(annotation)[0])
    return result


def to_class_signature(cls: type[Any], /) -> inspect.Signature | None:
    try:
        return inspect.signature(cls)
    except (TypeError, ValueError):
        return None


def to_lists_different_elements(
    left: list[_T], right: list[_T], /
) -> list[tuple[_T | None, _T | None]]:
    return [
        (left_element, right_element)
        for left_element, right_element in zip_longest(left, right)
        if left_element != right_element
    ]


_T = TypeVar('_T')


def to_self_referential_class_fields(cls: type[_T], /) -> dict[str, _T]:
    return {
        name: value
        for name, value in vars(cls).items()
        if isinstance(value, cls)
    }


class _Sortable(Protocol):
    def __lt__(self, other: Self, /) -> bool: ...


_SortableT = TypeVar('_SortableT', bound=_Sortable)


def to_sorted_lists_diff(
    left: list[_SortableT], right: list[_SortableT], /
) -> list[tuple[_SortableT | None, _SortableT | None]]:
    result: list[tuple[_SortableT | None, _SortableT | None]] = []
    left_elements, right_elements = iter(left), iter(right)
    try:
        left_element = next(left_elements)
    except StopIteration:
        result.extend(
            (None, right_element) for right_element in right_elements
        )
    else:
        while True:
            try:
                while (right_element := next(right_elements)) < left_element:
                    result.append((None, right_element))
            except StopIteration:  # noqa: PERF203
                result.append((left_element, None))
                result.extend(
                    (left_element, None) for left_element in left_elements
                )
                break
            else:
                if right_element == left_element:
                    try:
                        left_element = next(left_elements)
                    except StopIteration:
                        result.extend(
                            (None, right_element)
                            for right_element in right_elements
                        )
                        break
                    else:
                        continue
                result.append((left_element, None))
                assert left_element < right_element, (
                    left_element,
                    right_element,
                )
                try:
                    while (
                        left_element := next(left_elements)
                    ) < right_element:
                        result.append((left_element, None))
                except StopIteration:
                    result.append((None, right_element))
                    result.extend(
                        (None, right_element)
                        for right_element in right_elements
                    )
                    break
                else:
                    if left_element == right_element:
                        try:
                            left_element = next(left_elements)
                        except StopIteration:
                            result.extend(
                                (None, right_element)
                                for right_element in right_elements
                            )
                            break
                        else:
                            continue
                    result.append((None, right_element))
                    assert right_element < left_element, (
                        left_element,
                        right_element,
                    )
    return result


def to_typeless_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
    result = parameter.replace(annotation=inspect.Parameter.empty)
    return (
        result
        if result.default is inspect.Parameter.empty
        else result.replace(default=None)
    )


def to_unsorted_list_elements(
    value: list[_T], /, *, key: Callable[[_T], _SortableT]
) -> list[tuple[_T, _T]]:
    return [
        (element, sorted_element)
        for element, sorted_element in zip(value, sorted(value, key=key))
        if element != sorted_element
    ]