def _check_type_param(self, params: Dict[str, inspect.Parameter]) -> None:
        arg_index = 1 if self.func.is_instance_method else 0

        for key, param in params.items():
            self._already_checked_kwargs.append(key)
            self._assert_param_has_type_annotation(param=param)

            if param.default is inspect.Signature.empty:
                if self.func.should_have_kwargs:
                    if key not in self.kwargs:
                        raise PedanticTypeCheckException(
                            f'{self.func.err}Parameter "{key}" is unfilled.')
                    actual_value = self.kwargs[key]
                else:
                    actual_value = self.args[arg_index]
                    arg_index += 1
            else:
                if key in self.kwargs:
                    actual_value = self.kwargs[key]
                else:
                    actual_value = param.default

            _assert_value_matches_type(value=actual_value,
                                       type_=param.annotation,
                                       err=self.func.err,
                                       type_vars=self.type_vars,
                                       key=key)
    def __init__(self, func: Callable[..., Any]) -> None:
        self._func = func

        if not isinstance(func, (types.FunctionType, types.MethodType)):
            raise PedanticTypeCheckException(f'{self.full_name} should be a method or function.')

        self._full_arg_spec = inspect.getfullargspec(func)
        self._signature = inspect.signature(func)
        self._err = f'In function {func.__qualname__}:' + '\n'
        self._source: str = inspect.getsource(object=func)
        self._docstring = parse(func.__doc__)
    def _set_and_check_return_types(self, expected_return_type: Any) -> Any:
        base_generic = _get_base_generic(cls=expected_return_type)

        if base_generic not in [Generator, Iterable, Iterator]:
            raise PedanticTypeCheckException(
                f'{self._err}Generator should have type annotation "typing.Generator[]", "typing.Iterator[]" or '
                f'"typing.Iterable[]". Got "{expected_return_type}" instead.')

        result = _get_type_arguments(expected_return_type)

        if len(result) == 1:
            self._yield_type = result[0]
        elif len(result) == 3:
            self._yield_type = result[0]
            self._send_type = result[1]
            self._return_type = result[2]
        else:
            raise PedanticTypeCheckException(
                f'{self._err}Generator should have a type argument. Got: {result}'
            )
        return result[0]
    def decorate(cls: C) -> C:
        if not is_enabled():
            return cls

        if issubclass(cls, enum.Enum):
            raise PedanticTypeCheckException(
                f'Enum "{cls}" cannot be decorated with "@pedantic_class". '
                f'Enums are not supported yet.')

        if sys.version_info >= (3, 7):
            from dataclasses import is_dataclass

            if is_dataclass(obj=cls):
                raise PedanticTypeCheckException(
                    f'Dataclass "{cls}" cannot be decorated with "@pedantic_class". '
                    f'Try to write "@dataclass" over "@pedantic_class".')

        for attr in cls.__dict__:
            attr_value = getattr(cls, attr)

            if isinstance(attr_value, (types.FunctionType, types.MethodType)):
                setattr(cls, attr, decorator(attr_value))
            elif isinstance(attr_value, property):
                prop = attr_value
                wrapped_getter = _get_wrapped(prop=prop.fget,
                                              decorator=decorator)
                wrapped_setter = _get_wrapped(prop=prop.fset,
                                              decorator=decorator)
                wrapped_deleter = _get_wrapped(prop=prop.fdel,
                                               decorator=decorator)
                new_prop = property(fget=wrapped_getter,
                                    fset=wrapped_setter,
                                    fdel=wrapped_deleter)
                setattr(cls, attr, new_prop)

        _add_type_var_attr_and_method_to_class(cls=cls)
        return cls
def _assert_value_matches_type(value: Any,
                               type_: Any,
                               err: str,
                               type_vars: Dict[TypeVar_, Any],
                               key: Optional[str] = None,
                               msg: Optional[str] = None
                               ) -> None:
    if not _check_type(value=value, type_=type_, err=err, type_vars=type_vars):
        t = type(value)
        value = f'{key}={value}' if key is not None else str(value)

        if not msg:
            msg = f'{err}Type hint is incorrect: Argument {value} of type {t} does not match expected type {type_}.'

        raise PedanticTypeCheckException(msg)
    def _check_types_return(self,
                            result: Any) -> Union[None, GeneratorWrapper]:
        if self.func.signature.return_annotation is inspect.Signature.empty:
            raise PedanticTypeCheckException(
                f'{self.func.err}There should be a type hint for the return type (e.g. None if nothing is returned).'
            )

        expected_result_type = self.func.annotations['return']

        if self.func.is_generator:
            return GeneratorWrapper(wrapped=result,
                                    expected_type=expected_result_type,
                                    err_msg=self.func.err,
                                    type_vars=self.type_vars)

        msg = f'{self.func.err}Type hint of return value is incorrect: Expected type {expected_result_type} ' \
              f'but {result} of type {type(result)} was the return value which does not match.'
        _assert_value_matches_type(value=result,
                                   type_=expected_result_type,
                                   err=self.func.err,
                                   type_vars=self.type_vars,
                                   msg=msg)
        return result
 def _assert_param_has_type_annotation(self, param: inspect.Parameter):
     if param.annotation == inspect.Parameter.empty:
         raise PedanticTypeCheckException(
             f'{self.func.err}Parameter "{param.name}" should have a type hint.'
         )
def _check_type(value: Any, type_: Any, err: str, type_vars: Dict[TypeVar_, Any]) -> bool:
    """
        >>> from typing import List, Union, Optional, Callable, Any
        >>> _check_type(5, int, '', {})
        True
        >>> _check_type(5, float, '', {})
        False
        >>> _check_type('hi', str, '', {})
        True
        >>> _check_type(None, str, '', {})
        False
        >>> _check_type(None, Any, '', {})
        True
        >>> _check_type(None, None, '', {})
        True
        >>> _check_type(5, Any, '', {})
        True
        >>> _check_type(3.1415, float, '', {})
        True
        >>> _check_type([1, 2, 3, 4], List[int], '', {})
        True
        >>> _check_type([1, 2, 3.0, 4], List[int], '', {})
        False
        >>> _check_type([1, 2, 3.0, 4], List[float], '', {})
        False
        >>> _check_type([1, 2, 3.0, 4], List[Union[float, int]], '', {})
        True
        >>> _check_type([[True, False], [False], [True], []], List[List[bool]], '', {})
        True
        >>> _check_type([[True, False, 1], [False], [True], []], List[List[bool]], '', {})
        False
        >>> _check_type(5, Union[int, float, bool], '', {})
        True
        >>> _check_type(5.0, Union[int, float, bool], '', {})
        True
        >>> _check_type(False, Union[int, float, bool], '', {})
        True
        >>> _check_type('5', Union[int, float, bool], '', {})
        False
        >>> def f(a: int, b: bool, c: str) -> float: pass
        >>> _check_type(f, Callable[[int, bool, str], float], '', {})
        True
        >>> _check_type(None, Optional[List[Dict[str, float]]], '', {})
        True
        >>> _check_type([{'a': 1.2, 'b': 3.4}], Optional[List[Dict[str, float]]], '', {})
        True
        >>> _check_type([{'a': 1.2, 'b': 3}], Optional[List[Dict[str, float]]], '', {})
        False
        >>> _check_type({'a': 1.2, 'b': 3.4}, Optional[List[Dict[str, float]]], '', {})
        False
        >>> _check_type([{'a': 1.2, 7: 3.4}], Optional[List[Dict[str, float]]], '', {})
        False
        >>> class MyClass: pass
        >>> _check_type(MyClass(), 'MyClass', '', {})
        True
        >>> _check_type(MyClass(), 'MyClas', '', {})
        False
        >>> _check_type([1, 2, 3], list, '', {})
        Traceback (most recent call last):
        ...
        pedantic.exceptions.PedanticTypeCheckException: Use "List[]" instead of "list" as type hint.
        >>> _check_type((1, 2, 3), tuple, '', {})
        Traceback (most recent call last):
        ...
        pedantic.exceptions.PedanticTypeCheckException: Use "Tuple[]" instead of "tuple" as type hint.
        >>> _check_type({1: 1.0, 2: 2.0, 3: 3.0}, dict, '', {})
        Traceback (most recent call last):
        ...
        pedantic.exceptions.PedanticTypeCheckException: Use "Dict[]" instead of "dict" as type hint.
    """

    if type_ is None:
        return value == type_
    elif isinstance(type_, str):
        class_name = value.__class__.__name__
        base_class_name = value.__class__.__base__.__name__
        return class_name == type_ or base_class_name == type_

    if isinstance(type_, tuple):
        raise PedanticTypeCheckException(f'{err}Use "Tuple[]" instead of "{type_}" as type hint.')
    if isinstance(type_, list):
        raise PedanticTypeCheckException(f'{err}Use "List[]" instead of "{type_}" as type hint.')
    if type_ is tuple:
        raise PedanticTypeCheckException(f'{err}Use "Tuple[]" instead of "tuple" as type hint.')
    if type_ is list:
        raise PedanticTypeCheckException(f'{err}Use "List[]" instead of "list" as type hint.')
    if type_ is dict:
        raise PedanticTypeCheckException(f'{err}Use "Dict[]" instead of "dict" as type hint.')
    if type_ is set:
        raise PedanticTypeCheckException(f'{err}Use "Set[]" instead of "set" as type hint.')
    if type_ is frozenset:
        raise PedanticTypeCheckException(f'{err}Use "FrozenSet[]" instead of "frozenset" as type hint.')
    if type_ is type:
        raise PedanticTypeCheckException(f'{err}Use "Type[]" instead of "type" as type hint.')

    try:
        return _is_instance(obj=value, type_=type_, type_vars=type_vars)
    except PedanticTypeCheckException as ex:
        raise PedanticTypeCheckException(f'{err} {ex}')
    except PedanticTypeVarMismatchException as ex:
        raise PedanticTypeVarMismatchException(f'{err} {ex}')
    except (AttributeError, Exception) as ex:
        raise PedanticTypeCheckException(
            f'{err}An error occurred during type hint checking. Value: {value} Annotation: '
            f'{type_} Mostly this is caused by an incorrect type annotation. Details: {ex} ')
def _is_instance(obj: Any, type_: Any, type_vars: Dict[TypeVar_, Any]) -> bool:
    if not _has_required_type_arguments(type_):
        raise PedanticTypeCheckException(
            f'The type annotation "{type_}" misses some type arguments e.g. '
            f'"typing.Tuple[Any, ...]" or "typing.Callable[..., str]".')

    if type_.__module__ == 'typing':
        if _is_generic(type_):
            origin = _get_base_generic(type_)
        else:
            origin = type_

        name = _get_name(origin)

        if name in _SPECIAL_INSTANCE_CHECKERS:
            validator = _SPECIAL_INSTANCE_CHECKERS[name]
            return validator(obj, type_, type_vars)

    if type_ == typing.BinaryIO:
        return isinstance(obj, (BytesIO, BufferedWriter))
    elif type_ == typing.TextIO:
        return isinstance(obj, (StringIO, TextIOWrapper))

    if _is_generic(type_):
        python_type = type_.__origin__

        if not isinstance(obj, python_type):
            return False

        base = _get_base_generic(type_)
        type_args = _get_type_arguments(cls=type_)

        if base in _ORIGIN_TYPE_CHECKERS:
            validator = _ORIGIN_TYPE_CHECKERS[base]
            return validator(obj, type_args, type_vars)

        assert base.__base__ == typing.Generic, f'Unknown base: {base}'
        return isinstance(obj, base)

    if isinstance(type_, TypeVar):
        constraints = type_.__constraints__

        if len(constraints) > 0 and type(obj) not in constraints:
            return False

        if _is_forward_ref(type_=type_.__bound__):
            return type(obj).__name__ == type_.__bound__.__forward_arg__

        if type_.__bound__ is not None and not isinstance(obj, type_.__bound__):
            return False

        if type_ in type_vars:
            other = type_vars[type_]

            if type_.__contravariant__:
                if not _is_subtype(sub_type=other, super_type=obj.__class__):
                    raise PedanticTypeVarMismatchException(
                        f'For TypeVar {type_} exists a type conflict: value {obj} has type {type(obj)} but TypeVar {type_} '
                        f'was previously matched to type {other}')
            else:
                if not _is_instance(obj=obj, type_=other, type_vars=type_vars):
                    raise PedanticTypeVarMismatchException(
                        f'For TypeVar {type_} exists a type conflict: value {obj} has type {type(obj)} but TypeVar {type_} '
                        f'was previously matched to type {other}')

        type_vars[type_] = type(obj)
        return True

    if _is_forward_ref(type_=type_):
        return type(obj).__name__ == type_.__forward_arg__

    if _is_type_new_type(type_):
        return isinstance(obj, type_.__supertype__)

    if hasattr(obj, '_asdict'):
        if hasattr(type_, '_field_types'):
            field_types = type_._field_types
        elif hasattr(type_, '__annotations__'):
            field_types = type_.__annotations__
        else:
            return False

        if not obj._asdict().keys() == field_types.keys():
            return False

        return all([_is_instance(obj=obj._asdict()[k], type_=v, type_vars=type_vars) for k, v in field_types.items()])

    return isinstance(obj, type_)