<commit_before>#!/usr/bin/env python3
from functools import wraps
import inspect
from typing import (Union,
                    Tuple,
                    Any,
                    TypeVar,
                    Type,
                    List)


def check_type(obj, candidate_type, reltype='invariant') -> bool:
    if reltype not in ['invariant', 'covariant', 'contravariant']:
        raise ValueError(f' Variadic type {reltype} is unknown')

    # builtin type like str, or a class
    if type(candidate_type) == type and reltype in ['invariant']:
        return isinstance(obj, candidate_type)

    if type(candidate_type) == type and reltype in ['covariant']:
        return issubclass(obj.__class__, candidate_type)

    if type(candidate_type) == type and reltype in ['contravariant']:
        return issubclass(candidate_type, obj.__class__)

    # Any accepts everything
    if type(candidate_type) == type(Any):
        return True

    # Union, at least one match in __args__
    if type(candidate_type) == type(Union):
        return any(check_type(obj, t, reltype) for t in candidate_type.__args__)

    # Tuple, each element matches the corresponding type in __args__
    if type(candidate_type) == type(Tuple):
        if not hasattr(obj, '__len__'):
            return False
        if len(candidate_type.__args__) != len(obj):
            return False
        return all(check_type(o, t, reltype) for (o, t) in zip(obj, candidate_type.__args__))

    # List, each element matches the type in __args__
    if type(candidate_type) == type(List):
        if not hasattr(obj, '__len__'):
            return False
        return all(check_type(o, candidate_type.__args__[0], reltype) for o in obj)

    # TypeVar, this is tricky
    if type(candidate_type) == type(TypeVar):
        # TODO consider contravariant, variant and bound
        # invariant with a list of constraints, acts like a Tuple
        if not (candidate_type.__covariant__ or candidate_type.__contracovariant__) and len(
                candidate_type.__constraints__) > 0:
            return any(check_type(obj, t) for t in candidate_type.__constraints__)

    if type(candidate_type) == type(Type):
        return check_type(obj, candidate_type.__args__[0], reltype='covariant')

    raise ValueError(f'Cannot check against {reltype} type {candidate_type}')


def check_args(func):
    @wraps(func)
    def check(*args):
        sig = inspect.signature(func)
        params = zip(sig.parameters, args)
        for name, value in params:
            if not check_type(value, sig.parameters[name].annotation):
                raise TypeError(f'Expected {sig.parameters[name]}, got {type(value)}.')
        return func(*args)

    return check
<commit_msg>Raise exceptions with list of issues to show all of them immediately<commit_after>#!/usr/bin/env python3
from functools import wraps
import inspect
from typing import (Union,
                    Tuple,
                    Any,
                    TypeVar,
                    Type,
                    List)


class DetailedTypeError(TypeError):
    issues = []

    def __init__(self, issues: List[Tuple[str, str, str]]):
        self.issues = issues
        super().__init__(f'typing issues found:{issues}')


def check_type(obj: Any,
               candidate_type: Any,
               reltype: str='invariant') -> bool:
    if reltype not in ['invariant', 'covariant', 'contravariant']:
        raise ValueError(f' Variadic type {reltype} is unknown')

    # builtin type like str, or a class
    if type(candidate_type) == type and reltype in ['invariant']:
        return isinstance(obj, candidate_type)

    if type(candidate_type) == type and reltype in ['covariant']:
        return issubclass(obj.__class__, candidate_type)

    if type(candidate_type) == type and reltype in ['contravariant']:
        return issubclass(candidate_type, obj.__class__)

    # Any accepts everything
    if type(candidate_type) == type(Any):
        return True

    # Union, at least one match in __args__
    if type(candidate_type) == type(Union):
        return any(check_type(obj, t, reltype) for t in candidate_type.__args__)

    # Tuple, each element matches the corresponding type in __args__
    if type(candidate_type) == type(Tuple):
        if not hasattr(obj, '__len__'):
            return False
        if len(candidate_type.__args__) != len(obj):
            return False
        return all(check_type(o, t, reltype) for (o, t) in zip(obj, candidate_type.__args__))

    # List, each element matches the type in __args__
    if type(candidate_type) == type(List):
        if not hasattr(obj, '__len__'):
            return False
        return all(check_type(o, candidate_type.__args__[0], reltype) for o in obj)

    # TypeVar, this is tricky
    if type(candidate_type) == type(TypeVar):
        # TODO consider contravariant, variant and bound
        # invariant with a list of constraints, acts like a Tuple
        if not (candidate_type.__covariant__ or candidate_type.__contracovariant__) and len(
                candidate_type.__constraints__) > 0:
            return any(check_type(obj, t) for t in candidate_type.__constraints__)

    if type(candidate_type) == type(Type):
        return check_type(obj, candidate_type.__args__[0], reltype='covariant')

    raise ValueError(f'Cannot check against {reltype} type {candidate_type}')


def check_args(func):
    @wraps(func)
    def check(*args):
        sig = inspect.signature(func)
        params = zip(sig.parameters, args)
        found_errors = []
        for name, value in params:
            if not check_type(value, sig.parameters[name].annotation):
                found_errors.append((name, sig.parameters[name].annotation, value))
        if len(found_errors) > 0:
            raise DetailedTypeError(found_errors)
        return func(*args)

    return check

{'fraction_non_alphanumeric': 0.09683458134785568, 'fraction_numerical': 0.0015316541865214432, 'mean_word_length': 2.750478621569879, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6256183', 'n_tokens_mistral': 1729, 'n_tokens_neox': 1658, 'n_words': 508}