Source code for typepy.type._base

"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""

import abc
from typing import Any, Optional

from .._typecode import Typecode
from ..checker._interface import TypeCheckerInterface
from ..converter import ValueConverterInterface
from ..error import TypeConversionError


class AbstractType(TypeCheckerInterface, ValueConverterInterface):
    __slots__ = (
        "_data",
        "_strict_level",
        "_params",
        "__checker",
        "__converter",
        "__is_type_result",
    )

    @abc.abstractproperty
    def typecode(self) -> Typecode:  # pragma: no cover
        pass

    @abc.abstractmethod
    def _create_type_checker(self):  # pragma: no cover
        pass

    @abc.abstractmethod
    def _create_type_converter(self):  # pragma: no cover
        pass

    @property
    def typename(self) -> str:
        return self.typecode.name

    def __init__(self, value: Any, strict_level: int, **kwargs) -> None:
        self._data = value
        self._strict_level = strict_level
        self._params = kwargs

        self.__checker = self._create_type_checker()
        self.__converter = self._create_type_converter()

        self.__is_type_result: Optional[bool] = None

    def __repr__(self) -> str:
        return ", ".join(
            [
                f"value={self._data}",
                f"typename={self.typename}",
                f"strict_level={self._strict_level}",
                f"is_type={self.is_type()}",
                f"try_convert={self.try_convert()}",
            ]
        )

    def is_type(self) -> bool:
        """
        :return:
        :rtype: bool
        """

        if self.__is_type_result is not None:
            return self.__is_type_result

        self.__is_type_result = self.__is_type()

        return self.__is_type_result

    def __is_type(self) -> bool:
        if self.__checker.is_type():
            return True

        if self.__checker.is_exclude_instance():
            return False

        try:
            self._converted_value = self.__converter.force_convert()
        except TypeConversionError:
            return False

        if not self.__checker.is_valid_after_convert(self._converted_value):
            return False

        return True

    def validate(self, error_message: Optional[str] = None) -> None:
        """
        :raises TypeError:
            If the value is not matched the type that the class represented.
        """

        if self.is_type():
            return

        if not error_message:
            error_message = "invalid value type"

        raise TypeError(f"{error_message}: expected={self.typename}, actual={type(self._data)}")

    def convert(self):
        """
        :return: Converted value.
        :raises typepy.TypeConversionError:
            If the value cannot convert.
        """

        if self.is_type():
            return self.force_convert()

        raise TypeConversionError(
            "failed to convert {} from {} to {}".format(
                self._data, type(self._data).__name__, self.typename
            )
        )

    def force_convert(self):
        """
        :return: Converted value.
        :raises typepy.TypeConversionError:
            If the value cannot convert.
        """

        return self.__converter.force_convert()

    def try_convert(self):
        """
        :return: Converted value. |None| if failed to convert.
        """

        try:
            return self.convert()
        except TypeConversionError:
            return None