Source code for structured_data._adt.product_type

"""Internal implementation of the Product base class."""

import inspect
import typing

import typing_extensions

from .. import _cant_modify
from .. import _conditional_method
from . import annotations
from . import constructor
from . import ordering
from . import prewritten_methods

TProduct = typing.TypeVar("TProduct", bound="Product")


class Named(typing_extensions.Protocol):

    __name__: str


class MethodLike(typing_extensions.Protocol):
    def __get__(self, instance: typing.Any, owner: type) -> Named:
        """Methods allow for binding and have names."""


def name_(cls: type, function: MethodLike) -> str:
    """Return the name of a function accessed through a descriptor."""
    return function.__get__(None, cls).__name__


def cant_set_new_functions(
    cls: type, *functions: typing.Callable
) -> typing.Optional[str]:
    """Determine if attributes corresponding to functions on cls could be set.

    If any attributes are already defined, return the already-defined name.
    """
    for function in functions:
        name = name_(cls, typing.cast(MethodLike, function))
        existing = getattr(cls, name, None)
        if existing not in (
            getattr(object, name, None),
            getattr(Product, name, None),
            None,
            function,
        ):
            return name
    return None


def _product_new(_cls: typing.Type[TProduct], _signature: inspect.Signature) -> None:
    signature: inspect.Signature
    if "__new__" in vars(_cls):
        original_new = _cls.__new__

        def __new__(
            cls: typing.Type[TProduct],
            /,  # noqa: E225
            *args: typing.Any,
            **kwargs: typing.Any,
        ) -> TProduct:
            if cls is _cls:
                return original_new(cls, *args, **kwargs)
            return super(_cls, cls).__new__(cls, *args, **kwargs)

        signature = inspect.signature(original_new)
    else:

        def __new__(
            cls: typing.Type[TProduct],
            /,  # noqa: E225
            *args: typing.Any,
            **kwargs: typing.Any,
        ) -> TProduct:
            return super(_cls, cls).__new__(cls, *args, **kwargs)

        signature = _signature.replace(
            parameters=[inspect.Parameter("cls", inspect.Parameter.POSITIONAL_ONLY)]
            + list(_signature.parameters.values())
        )
    __new__.__signature__ = signature  # type: ignore
    _cls.__new__ = __new__  # type: ignore


def _product_signature(
    annotations_: typing.Dict[str, typing.Any], cls: typing.Type[TProduct]
) -> inspect.Signature:
    params = [
        inspect.Parameter(
            name,
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
            default=getattr(cls, name, inspect.Parameter.empty),
            annotation=annotation,
        )
        for (name, annotation) in annotations_.items()
    ]
    try:
        return inspect.Signature(parameters=params, return_annotation=cls)
    except ValueError:
        raise TypeError


[docs]class Product(constructor.ADTConstructor, tuple, constructor.ProductBase): """Base class of classes with typed fields. Examines PEP 526 __annotations__ to determine fields. If repr is true, a __repr__() method is added to the class. If order is true, rich comparison dunder methods are added. The Product class examines the class to find annotations. Annotations with a value of "None" are discarded. Fields may have default values, and can be set to inspect.empty to indicate "no default". The subclass is subclassable. The implementation was designed with a focus on flexibility over ideals of purity, and therefore provides various optional facilities that conflict with, for example, Liskov substitutability. For the purposes of matching, each class is considered distinct. """ __slots__ = () def __new__( cls: typing.Type[TProduct], /, # noqa: E225 *args: typing.Any, **kwargs: typing.Any, ) -> TProduct: if cls is Product: raise TypeError # Probably a result of not having positional-only args. bound_arguments = cls.__signature.bind( *args, **kwargs ) # pylint: disable=protected-access bound_arguments.apply_defaults() return super().__new__(cls, bound_arguments.arguments.values()) __repr: typing.ClassVar[bool] = True __eq: typing.ClassVar[bool] = True __order: typing.ClassVar[bool] = False __eq_succeeded = None __signature: typing.ClassVar[inspect.Signature] __fields: typing.ClassVar[typing.Dict[str, int]] @classmethod def __clear_nones(cls) -> None: if cls.__repr is None: del cls.__repr if cls.__eq is None: del cls.__eq if cls.__order is None: del cls.__order # Both of these are for consistency with modules defined in the stdlib. # BOOM! def __init_subclass__( cls, *, repr: typing.Optional[bool] = None, # pylint: disable=redefined-builtin eq: typing.Optional[bool] = None, # pylint: disable=invalid-name order: typing.Optional[bool] = None, **kwargs: typing.Any, ): super().__init_subclass__(**kwargs) # type: ignore cls.__repr = repr # type: ignore cls.__eq = eq # type: ignore cls.__order = order # type: ignore del repr, eq, order cls.__clear_nones() ordering.ordering_options_are_valid(eq=cls.__eq, order=cls.__order) annotations_ = annotations.product_args_from_annotations(cls) cls.__signature = _product_signature(annotations_, cls) cls.__fields = {field: index for (index, field) in enumerate(annotations_)} _product_new(cls, cls.__signature) source = prewritten_methods.PrewrittenProductMethods cls.__eq_succeeded = cls.__eq and not cant_set_new_functions( cls, source.__eq__, source.__ne__ ) ordering.raise_for_collision( ( cls.__order and ordering.can_set_ordering(can_set=cls.__eq_succeeded) and cant_set_new_functions( cls, source.__lt__, source.__le__, source.__gt__, source.__ge__ ) ), cls.__name__, ) def __dir__(self) -> typing.List[str]: super_dir: typing.List[str] super_dir = super().__dir__() # type: ignore return super_dir + list(self.__fields) def __getattribute__(self, name: str) -> typing.Any: index = object.__getattribute__(self, "_Product__fields").get(name) if index is None: return super().__getattribute__(name) return tuple.__getitem__(self, index) def __setattr__(self, name: str, value: typing.Any) -> None: _cant_modify.guard(self, name) super().__setattr__(name, value) def __delattr__(self, name: str) -> None: _cant_modify.guard(self, name) super().__delattr__(name) def __bool__(self) -> bool: return True source = prewritten_methods.PrewrittenProductMethods # pylint: disable=protected-access __repr__ = _conditional_method.conditional_method(source).__repr # type: ignore __hash__ = _conditional_method.conditional_method( # type: ignore source ).__eq_succeeded __eq__ = _conditional_method.conditional_method( # type: ignore source ).__eq_succeeded __ne__ = _conditional_method.conditional_method( # type: ignore source ).__eq_succeeded __lt__ = _conditional_method.conditional_method(source).__order # type: ignore __le__ = _conditional_method.conditional_method(source).__order # type: ignore __gt__ = _conditional_method.conditional_method(source).__order # type: ignore __ge__ = _conditional_method.conditional_method(source).__order # type: ignore del source