Ejemplo n.º 1
0
def get_cause_or_none_type_origin(sleuth: CauseSleuth) -> Optional[str]:
    '''
    Human-readable string describing the failure of the passed arbitrary object
    to satisfy the passed **PEP-compliant originative type hint** (i.e.,
    PEP-compliant type hint originating from a non-:mod:`typing` class) if this
    object actually fails to satisfy this hint *or* ``None`` otherwise (i.e.,
    if this object satisfies this hint).

    Parameters
    ----------
    sleuth : CauseSleuth
        Type-checking error cause sleuth.
    '''
    assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'

    # Origin type originating this hint if any *OR* "None" otherwise.
    hint_type_origin = get_hint_pep_stdlib_type_or_none(sleuth.hint)

    # If this hint does *NOT* originate from such a type, raise an exception.
    if hint_type_origin is None:
        raise _BeartypeCallHintPepRaiseException(
            f'{sleuth.exception_label} type hint '
            f'{repr(sleuth.hint)} not originated from an origin type.'
        )
    # Else, this hint originates from such a type.

    # Defer to the getter function handling non-"typing" classes. Presto!
    return get_cause_or_none_type(sleuth.permute(hint=hint_type_origin))
Ejemplo n.º 2
0
def get_cause_or_none_forwardref(sleuth: CauseSleuth) -> Optional[str]:
    '''
    Human-readable string describing the failure of the passed arbitrary object
    to satisfy the passed **forward reference type hint** (i.e., string whose
    value is the name of a user-defined class which has yet to be defined) if
    this object actually fails to satisfy this hint *or* ``None`` otherwise
    (i.e., if this object satisfies this hint).

    Parameters
    ----------
    sleuth : CauseSleuth
        Type-checking error cause sleuth.
    '''
    assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'
    assert sleuth.hint_sign is HINT_PEP484_BASE_FORWARDREF, (
        f'PEP type hint sign {repr(sleuth.hint_sign)} not forward reference.')

    # Fully-qualified classname referred to by this forward reference relative
    # to the decorated callable.
    hint_forwardref_classname = get_hint_forwardref_classname_relative_to_obj(
        obj=sleuth.func, hint=sleuth.hint)

    # User-defined class dynamically imported from this classname.
    hint_forwardref_class = import_module_attr(
        module_attr_name=hint_forwardref_classname,
        exception_label=(
            f'{sleuth.exception_label} forward reference type hint'),
        exception_cls=_BeartypeCallHintPepRaiseException,
    )

    # Defer to the getter function handling non-"typing" classes. Neato!
    return get_cause_or_none_type(sleuth.permute(hint=hint_forwardref_class))
Ejemplo n.º 3
0
def _get_cause_or_none_sequence(sleuth: CauseSleuth) -> 'Optional[str]':
    '''
    Human-readable string describing the failure of the passed arbitrary object
    to satisfy the passed **PEP-compliant possibly non-standard sequence type
    hint** (i.e., PEP-compliant type hint accepting one or more subscripted
    type hint arguments constraining *all* items of this object, which
    necessarily satisfies the :class:`collections.abc.Sequence` protocol with
    guaranteed ``O(1)`` indexation across all sequence items) if this object
    actually fails to satisfy this hint *or* ``None`` otherwise (i.e., if this
    object satisfies this hint).

    Parameters
    ----------
    sleuth : CauseSleuth
        Type-checking error cause sleuth.
    '''
    assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'
    assert (
        sleuth.hint_sign in HINT_PEP_SIGNS_SEQUENCE_STANDARD or (
            sleuth.hint_sign in HINT_PEP_SIGNS_TUPLE and
            len(sleuth.hint_childs) == 2 and
            sleuth.hint_childs[1] is Ellipsis
        )
    ), f'{repr(sleuth.hint)} neither standard sequence nor variadic tuple.'

    # First child hint of this hint. All remaining child hints if any are
    # ignorable. Specifically, if this hint is:
    # * A standard sequence (e.g., "typing.List[str]"), this hint is
    #   subscripted by only one child hint.
    # * A variadic tuple (e.g., "typing.Tuple[str, ...]"), this hint is
    #   subscripted by only two child hints the latter of which is ignorable
    #   syntactic chuff.
    hint_child = sleuth.hint_childs[0]

    # If this child hint is *NOT* ignorable...
    if not is_hint_ignorable(hint_child):
        # For each enumerated item of this pith...
        for pith_item_index, pith_item in enumerate(sleuth.pith):
            # Human-readable string describing the failure of this item to
            # satisfy this child hint if this item actually fails to satisfy
            # this child hint *or* "None" otherwise.
            pith_item_cause = sleuth.permute(
                pith=pith_item, hint=hint_child).get_cause_or_none()

            # If this item is the cause of this failure, return a substring
            # describing this failure by embedding this failure (itself
            # intended to be embedded in a longer string).
            if pith_item_cause is not None:
                return (
                    f'{sleuth.pith.__class__.__name__} item '
                    f'{pith_item_index} {pith_item_cause}')
            # Else, this item is *NOT* the cause of this failure. Silently
            # continue to the next.
    # Else, this child hint is ignorable.

    # Return "None", as all items of this pith are valid, implying this pith to
    # deeply satisfy this hint.
    return None
Ejemplo n.º 4
0
def get_cause_or_none_sequence_standard(
    sleuth: CauseSleuth) -> 'Optional[str]':
    '''
    Human-readable string describing the failure of the passed arbitrary object
    to satisfy the passed **PEP-compliant standard sequence type hint** (i.e.,
    PEP-compliant type hint accepting exactly one subscripted type hint
    argument constraining *all* items of this object, which necessarily
    satisfies the :class:`collections.abc.Sequence` protocol with guaranteed
    ``O(1)`` indexation across all sequence items) if this object actually
    fails to satisfy this hint *or* ``None`` otherwise (i.e., if this object
    satisfies this hint).

    Parameters
    ----------
    sleuth : CauseSleuth
        Type-checking error cause sleuth.
    '''
    assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'
    assert sleuth.hint_sign in HINT_PEP_SIGNS_SEQUENCE_STANDARD, (
        f'{repr(sleuth.hint)} not standard sequence.')

    # Assert this sequence was subscripted by exactly one argument. Note that
    # the "typing" module should have already guaranteed this on our behalf.
    assert len(sleuth.hint_childs) == 1, (
        f'Standard sequence {repr(sleuth.hint)} subscripted by '
        f'multiple arguments.')

    # Non-"typing" class originating this attribute (e.g., "list" for "List").
    hint_type_origin = get_hint_pep_type_origin(sleuth.hint)

    # If this pith is *NOT* an instance of this class, defer to the getter
    # function handling non-"typing" classes.
    if not isinstance(sleuth.pith, hint_type_origin):
        return get_cause_or_none_type(sleuth.permute(hint=hint_type_origin))

    # Else, this pith is an instance of this class and is thus a sequence.
    # Defer to the getter function supporting simple sequences.
    return _get_cause_or_none_sequence(sleuth)
Ejemplo n.º 5
0
def get_cause_or_none_generic(sleuth: CauseSleuth) -> 'Optional[str]':
    '''
    Human-readable string describing the failure of the passed arbitrary object
    to satisfy the passed `PEP 484`_-compliant **generic** (i.e., type hint
    subclassing a combination of one or more of the :mod:`typing.Generic`
    superclass, the :mod:`typing.Protocol` superclass, and/or other
    :mod:`typing` non-class pseudo-superclasses) if this object actually fails
    to satisfy this hint *or* ``None`` otherwise (i.e., if this object
    satisfies this hint).

    Parameters
    ----------
    sleuth : CauseSleuth
        Type-checking error cause sleuth.
    '''
    assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'
    assert isinstance(sleuth.hint, type), f'{repr(sleuth.hint)} not class.'
    assert sleuth.hint_sign is Generic, (
        f'{repr(sleuth.hint_sign)} not generic.')

    # If this pith is *NOT* an instance of this generic, defer to the getter
    # function handling non-"typing" classes.
    if not isinstance(sleuth.pith, sleuth.hint):
        return get_cause_or_none_type(sleuth)
    # Else, this pith is an instance of this generic.

    # For each pseudo-superclass of this generic...
    for hint_base in sleuth.hint_childs:
        # If this pseudo-superclass is an actual superclass, this
        # pseudo-superclass is effectively ignorable. Why? Because the
        # isinstance() call above already type-checked this pith against the
        # generic subclassing this superclass and thus this superclass as well.
        # In this case, skip to the next pseudo-superclass.
        if isinstance(hint_base, type):
            continue
        # Else, this pseudo-superclass is *NOT* an actual class.
        #
        # If this pseudo-superclass is neither a PEP 585-compliant type hint
        # *NOR* a PEP-compliant type hint defined by the "typing" module,
        # reduce this pseudo-superclass to a real superclass originating this
        # pseudo-superclass. See commentary in the "_pephint" submodule.
        elif not (is_hint_pep585(hint_base) and is_hint_pep_typing(hint_base)):
            hint_base = get_hint_pep484_generic_base_erased_from_unerased(
                hint_base)
        # Else, this pseudo-superclass is defined by the "typing" module.

        # If this superclass is ignorable, do so.
        if is_hint_ignorable(hint_base):
            continue
        # Else, this superclass is unignorable.

        # Human-readable string describing the failure of this pith to satisfy
        # this pseudo-superclass if this pith actually fails to satisfy
        # this pseudo-superclass *or* "None" otherwise.
        # print(f'tuple pith: {pith_item}\ntuple hint child: {hint_child}')
        pith_base_cause = sleuth.permute(hint=hint_base).get_cause_or_none()

        # If this pseudo-superclass is the cause of this failure, return a
        # substring describing this failure by embedding this failure (itself
        # intended to be embedded in a longer string).
        if pith_base_cause is not None:
            # print(f'tuple pith: {sleuth_copy.pith}\ntuple hint child: {sleuth_copy.hint}\ncause: {pith_item_cause}')
            return f'generic base {repr(hint_base)} {pith_base_cause}'
        # Else, this pseudo-superclass is *NOT* the cause of this failure.
        # Silently continue to the next.

    # Return "None", as this pith satisfies both this generic itself *AND* all
    # pseudo-superclasses subclassed by this generic, implying this pith to
    # deeply satisfy this hint.
    return None
Ejemplo n.º 6
0
def get_cause_or_none_union(sleuth: CauseSleuth) -> 'Optional[str]':
    '''
    Human-readable string describing the failure of the passed arbitrary object
    to satisfy the passed PEP-compliant union type hint if this object actually
    fails to satisfy this hint *or* ``None`` otherwise (i.e., if this object
    satisfies this hint).

    Parameters
    ----------
    sleuth : CauseSleuth
        Type-checking error cause sleuth.
    '''
    assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'
    assert sleuth.hint_sign in HINT_PEP484_SIGNS_UNION, (
        f'{repr(sleuth.hint)} not union.')

    # Subset of all classes shallowly associated with these child hints (i.e.,
    # by being either these child hints in the case of non-"typing" classes
    # *OR* the classes originating these child hints in the case of
    # PEP-compliant type hints) that this pith fails to shallowly satisfy.
    hint_classes_unsatisfied = set()

    # List of all human-readable strings describing the failure of this pith to
    # satisfy each of these child hints.
    causes_union = []

    # Indentation preceding each line of the strings returned by child getter
    # functions called by this parent getter function, offset to visually
    # demarcate child from parent causes in multiline strings.
    CAUSE_INDENT_CHILD = sleuth.cause_indent + '  '

    # For each subscripted argument of this union...
    for hint_child in sleuth.hint_childs:
        # If this child hint is ignorable, continue to the next.
        if is_hint_ignorable(hint_child):
            continue
        # Else, this child hint is unignorable.

        # If this child hint is PEP-compliant...
        if is_hint_pep(hint_child):
            # Non-"typing" class originating this child hint if any *OR* "None"
            # otherwise.
            hint_child_type_origin = get_hint_pep_type_origin_or_none(
                hint_child)

            # If...
            if (
                    # This child hint originates from a non-"typing" class *AND*...
                    hint_child_type_origin is not None and
                    # This pith is *NOT* an instance of this class...
                    not isinstance(sleuth.pith, hint_child_type_origin)
                    # Then this pith fails to satisfy this child hint. In this case...
            ):
                # Add this class to the subset of all classes this pith does
                # *NOT* satisfy.
                hint_classes_unsatisfied.add(hint_child_type_origin)

                # Continue to the next child hint.
                continue
            # Else, this pith is an instance of this class and thus shallowly
            # (but *NOT* necessarily deeply) satisfies this child hint.

            # Human-readable string describing the failure of this pith to
            # deeply satisfy this child hint if this pith actually fails to
            # deeply satisfy this child hint *or* "None" otherwise.
            pith_cause_hint_child = sleuth.permute(
                hint=hint_child,
                cause_indent=CAUSE_INDENT_CHILD,
            ).get_cause_or_none()

            # If this pith deeply satisfies this child hint, return "None".
            if pith_cause_hint_child is None:
                # print('Union child {!r} pith {!r} deeply satisfied!'.format(hint_child, pith))
                return None
            # Else, this pith does *NOT* deeply satisfy this child hint.

            # Append a cause as a discrete bullet-prefixed line.
            causes_union.append(pith_cause_hint_child)
        # Else, this child hint is PEP-noncompliant. In this case...
        else:
            # Assert this child hint to be a non-"typing" class. Note that
            # the "typing" module should have already guaranteed that all
            # subscripted arguments of unions are either PEP-compliant type
            # hints or non-"typing" classes.
            assert isinstance(hint_child, type), (
                f'{sleuth.exception_label} PEP union type hint '
                f'{repr(sleuth.hint)} child hint {repr(hint_child)} invalid '
                f'(i.e., neither PEP type hint nor non-"typing" class).')
            # Else, this child hint is a non-"typing" type.

            # If this pith is an instance of this class, this pith satisfies
            # this hint. In this case, return "None".
            if isinstance(sleuth.pith, hint_child):
                return None

            # Else, this pith is *NOT* an instance of this class, implying this
            # pith to *NOT* satisfy this hint. In this case, add this class to
            # the subset of all classes this pith does *NOT* satisfy.
            hint_classes_unsatisfied.add(hint_child)

    # If this pith fails to shallowly satisfy one or more classes, concatenate
    # these failures onto a discrete bullet-prefixed line.
    if hint_classes_unsatisfied:
        # Human-readable comma-delimited disjunction of the names of these
        # classes (e.g., "bool, float, int, or str").
        cause_types_unsatisfied = join_delimited_disjunction_classes(
            hint_classes_unsatisfied)

        # Prepend this cause as a discrete bullet-prefixed line.
        #
        # Note that this cause is intentionally prependend rather than appended
        # to this list. Since this cause applies *ONLY* to the shallow type of
        # the current pith rather than any items contained in this pith,
        # listing this shallow cause *BEFORE* other deeper causes typically
        # applying to items contained in this pith produces substantially more
        # human-readable exception messages: e.g.,
        #     # This reads well.
        #     @beartyped pep_hinted() parameter pep_hinted_param=(1,) violates
        #     PEP type hint typing.Union[int, typing.Sequence[str]], as (1,):
        #     * Not int.
        #     * Tuple item 0 value "1" not str.
        #
        #     # This does not.
        #     @beartyped pep_hinted() parameter pep_hinted_param=(1,) violates
        #     PEP type hint typing.Union[int, typing.Sequence[str]], as (1,):
        #     * Tuple item 0 value "1" not str.
        #     * Not int.
        #
        # Note that prepending to lists is an O(n) operation, but that this
        # cost is negligible in this case both due to the negligible number of
        # child hints of the average "typing.Union" in general *AND* due to the
        # fact that this function is only called when a catastrophic type-check
        # failure has already occurred.
        causes_union.insert(0, f'not {cause_types_unsatisfied}')

    # If prior logic appended *NO* causes, raise an exception.
    if not causes_union:
        raise _BeartypeCallHintPepRaiseException(
            f'{sleuth.exception_label} PEP type hint '
            f'{repr(sleuth.hint)} failure causes unknown.')
    # Else, prior logic appended one or more strings describing these failures.

    # Truncated object representation of this pith.
    pith_repr = get_object_representation(sleuth.pith)

    # If prior logic appended one cause, return this cause as a single-line
    # substring intended to be embedded in a longer string.
    if len(causes_union) == 1:
        return f'{pith_repr} {causes_union[0]}'
    # Else, prior logic appended two or more causes.

    # Return a multiline string comprised of...
    return '{}:\n{}'.format(
        # This truncated object representation.
        pith_repr,
        # The newline-delimited concatenation of each cause as a discrete
        # bullet-prefixed line...
        '\n'.join(
            '{}* {}'.format(
                # Indented by the current indent.
                sleuth.cause_indent,
                # Whose first character is uppercased.
                uppercase_char_first(
                    # Suffixed by a period if *NOT* yet suffixed by a period.
                    suffix_unless_suffixed(text=cause_union, suffix='.')))
            # '{}* {}.'.format(cause_indent, uppercase_char_first(cause_union))
            for cause_union in causes_union))
Ejemplo n.º 7
0
def raise_pep_call_exception(
    func: 'CallableTypes',
    pith_name: str,
    pith_value: object,
) -> None:
    '''
    Raise a human-readable exception detailing the failure of the parameter
    with the passed name *or* return value if this name is the magic string
    ``return`` of the passed decorated function fails to satisfy the
    PEP-compliant type hint annotated on this parameter or return value.

    Design
    ----------
    The :mod:`beartype` package actually implements two parallel PEP-compliant
    runtime type-checkers, each complementing the other by providing
    functionality unsuited for the other. These are:

    * The :mod:`beartype._decor._code._pep` submodule, dynamically generating
      optimized PEP-compliant runtime type-checking code embedded in the body
      of the wrapper function wrapping the decorated callable. For both
      efficiency and maintainability, that code only tests whether or not a
      parameter passed to that callable or value returned from that callable
      satisfies a PEP-compliant annotation on that callable; that code does
      *not* raise human-readable exceptions in the event that value fails to
      satisfy that annotation. Instead, that code defers to...
    * This function, performing unoptimized PEP-compliant runtime type-checking
      generically applicable to all wrapper functions. The aforementioned
      code calls this function only in the event that value fails to satisfy
      that annotation, in which case this function then raises a human-readable
      exception after discovering the underlying cause of this type failure by
      recursively traversing that value and annotation. While efficiency is the
      foremost focus of this package, efficiency is irrelevant during exception
      handling -- which typically only occurs under infrequent edge cases.
      Likewise, while raising this exception *would* technically be feasible
      from the aforementioned code, doing so proved sufficiently non-trivial,
      fragile, and ultimately unmaintainable to warrant offloading to this
      function universally callable from all wrapper functions.

    Parameters
    ----------
    func : CallableTypes
        Decorated callable to raise this exception from.
    pith_name : str
        Either:

        * If the object failing to satisfy this hint is a passed parameter, the
          name of this parameter.
        * Else, the magic string ``return`` implying this object to be the
          value returned from this callable.
    pith_value : object
        Passed parameter or returned value failing to satisfy this hint.

    Raises
    ----------
    BeartypeCallHintPepParamException
        If the object failing to satisfy this hint is a parameter.
    BeartypeCallHintPepReturnException
        If the object failing to satisfy this hint is a return value.
    BeartypeDecorHintPepException
        If the type hint annotating this object is *not* PEP-compliant.
    _BeartypeCallHintPepRaiseException
        If the parameter or return value with the passed name is unannotated.
    _BeartypeCallHintPepRaiseDesynchronizationException
        If this pith actually satisfies this hint, implying either:

        * The parent wrapper function generated by the :mod:`beartype.beartype`
          decorator type-checking this pith triggered a false negative by
          erroneously misdetecting this pith as failing this type check.
        * This child helper function re-type-checking this pith triggered a
          false positive by erroneously misdetecting this pith as satisfying
          this type check when in fact this pith fails to do so.
    '''
    assert callable(func), f'{repr(func)} uncallable.'
    assert isinstance(pith_name, str), f'{repr(pith_name)} not string.'
    # print('''raise_pep_call_exception(
    #     func={!r},
    #     pith_name={!r},
    #     pith_value={!r}',
    # )'''.format(func, pith_name, pith_value))

    # Type of exception to be raised.
    exception_cls = None

    # Human-readable label describing this parameter or return value.
    pith_label = None

    # If the name of this parameter is the magic string implying the passed
    # object to be a return value, set the above local variables appropriately.
    if pith_name == 'return':
        exception_cls = BeartypeCallHintPepReturnException
        pith_label = label_callable_decorated_return_value(
            func=func, return_value=pith_value)
    # Else, the passed object is a parameter. In this case, set the above local
    # variables appropriately.
    else:
        exception_cls = BeartypeCallHintPepParamException
        pith_label = label_callable_decorated_param_value(
            func=func,
            param_name =pith_name,
            param_value=pith_value,
        )

    # If this parameter or return value is unannotated, raise an exception.
    #
    # Note that this should *NEVER* occur, as the caller guarantees this
    # parameter or return value to be annotated. Nonetheless, since callers
    # could deface the "__annotations__" dunder dictionary without our
    # knowledge or permission, precautions are warranted.
    if pith_name not in func.__annotations__:
        raise _BeartypeCallHintPepRaiseException(f'{pith_label} unannotated.')
    # Else, this parameter or return value is annotated.

    # PEP-compliant type hint annotating this parameter or return value.
    hint = func.__annotations__[pith_name]

    # If type hint is *NOT* a supported type hint, raise an exception.
    die_unless_hint(hint=hint, hint_label=f'{pith_label} type hint')
    # Else, this type hint is supported.

    # Human-readable string describing the failure of this pith to satisfy this
    # hint if this pith fails to satisfy this hint *OR* "None" otherwise (i.e.,
    # if this pith satisfies this hint).
    exception_cause = CauseSleuth(
        func=func,
        pith=pith_value,
        hint=hint,
        cause_indent='',
        exception_label=pith_label,
    ).get_cause_or_none()

    # If this pith does *NOT* satisfy this hint...
    if exception_cause:
        # This failure suffixed by a period if *NOT* yet suffixed by a period.
        exception_cause_suffixed = suffix_unless_suffixed(
            text=exception_cause, suffix='.')

        # Raise an exception of the desired class embedding this cause.
        raise exception_cls(
            f'{pith_label} violates type hint '
            f'{repr(hint)}, as {exception_cause_suffixed}'
        )

    # Else, this pith satisfies this hint. In this (hopefully uncommon) edge
    # case, *SOMETHING HAS GONE TERRIBLY AWRY.* In theory, this should never
    # happen, as the parent wrapper function performing type checking should
    # *ONLY* call this child helper function when this pith does *NOT* satisfy
    # this hint. In this case, raise an exception encouraging the end user to
    # submit an upstream issue with us.
    pith_value_repr = get_object_representation(
        obj=pith_value, max_len=_CAUSE_TRIM_OBJECT_REPR_MAX_LEN)
    raise _BeartypeCallHintPepRaiseDesynchronizationException(
        f'{pith_label} violates type hint {repr(hint)}, '
        f'but utility function raise_pep_call_exception() '
        f'suggests this object satisfies this hint. '
        f'Please report this desynchronization failure to '
        f'the beartype issue tracker ({URL_ISSUES}) with '
        f"this object's representation and "
        f'accompanying exception traceback:\n{pith_value_repr}'
    )
Ejemplo n.º 8
0
def get_cause_or_none_tuple(sleuth: CauseSleuth) -> 'Optional[str]':
    '''
    Human-readable string describing the failure of the passed arbitrary object
    to satisfy the passed **PEP-compliant standard sequence type hint** (i.e.,
    PEP-compliant type hint accepting exactly one subscripted type hint
    argument constraining *all* items of this object, which necessarily
    satisfies the :class:`collections.abc.Sequence` protocol with guaranteed
    ``O(1)`` indexation across all sequence items) if this object actually
    fails to satisfy this hint *or* ``None`` otherwise (i.e., if this object
    satisfies this hint).

    Parameters
    ----------
    sleuth : CauseSleuth
        Type-checking error cause sleuth.
    '''
    assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'
    assert sleuth.hint_sign in HINT_PEP_SIGNS_TUPLE, (
        f'{repr(sleuth.hint_sign)} not tuple.')

    # If this pith is *NOT* an instance of this class, defer to the getter
    # function handling non-"typing" classes.
    if not isinstance(sleuth.pith, tuple):
        return get_cause_or_none_type(sleuth.permute(hint=tuple))

    # If this hint is...
    if (
        # This tuple is subscripted by exactly two child hints *AND*...
        len(sleuth.hint_childs) == 2 and
        # The second child hint is just an unquoted ellipsis...
        sleuth.hint_childs[1] is Ellipsis
    ):
    # Then this hint is of the variadic form "Tuple[{typename}, ...]", typing a
    # tuple accepting a variadic number of items all satisfying the
    # child hint "{typename}". Since this case semantically reduces to a simple
    # sequence, defer to the getter function supporting simple sequences.
        return _get_cause_or_none_sequence(sleuth)
    # Else, this hint is of the fixed-length form "Tuple[{typename1}, ...,
    # {typenameN}]", typing a tuple accepting a fixed number of items each
    # satisfying a unique child hint.
    #
    # If this hint is the empty fixed-length tuple, validate this pith to be
    # the empty tuple.
    elif is_hint_pep_tuple_empty(sleuth.hint):
        # If this pith is non-empty and thus fails to satisfy this hint...
        if sleuth.pith:
            # Truncated representation of this tuple.
            pith_repr = get_object_representation(sleuth.pith)

            # Return a substring describing this failure.
            return f'tuple {pith_repr} non-empty'
        # Else, this pith is the empty tuple and thus satisfies this hint.
    # Else, this hint is a standard fixed-length tuple. In this case...
    else:
        # If this pith and hint are of differing lengths, this tuple fails to
        # satisfy this hint. In this case...
        if len(sleuth.pith) != len(sleuth.hint_childs):
            # Truncated representation of this tuple.
            pith_repr = get_object_representation(sleuth.pith)

            # Return a substring describing this failure.
            return (
                f'tuple {pith_repr} length '
                f'{len(sleuth.pith)} not {len(sleuth.hint_childs)}'
            )
        # Else, this pith and hint are of the same length.

        # For each enumerated item of this tuple...
        for pith_item_index, pith_item in enumerate(sleuth.pith):
            # Child hint corresponding to this tuple item. Since this pith and
            # hint are of the same length, this child hint exists.
            hint_child = sleuth.hint_childs[pith_item_index]

            # If this child hint is ignorable, continue to the next.
            if is_hint_ignorable(hint_child):
                continue
            # Else, this child hint is unignorable.

            # Human-readable string describing the failure of this tuple item
            # to satisfy this child hint if this item actually fails to satisfy
            # this child hint *or* "None" otherwise.
            # print(f'tuple pith: {pith_item}\ntuple hint child: {hint_child}')
            # sleuth_copy = sleuth.permute(pith=pith_item, hint=hint_child)
            # pith_item_cause = sleuth_copy.get_cause_or_none()
            pith_item_cause = sleuth.permute(
                pith=pith_item, hint=hint_child).get_cause_or_none()

            # If this item is the cause of this failure, return a substring
            # describing this failure by embedding this failure (itself
            # intended to be embedded in a longer string).
            if pith_item_cause is not None:
                # print(f'tuple pith: {sleuth_copy.pith}\ntuple hint child: {sleuth_copy.hint}\ncause: {pith_item_cause}')
                return f'tuple item {pith_item_index} {pith_item_cause}'
            # Else, this item is *NOT* the cause of this failure. Silently
            # continue to the next.

    # Return "None", as all items of this fixed-length tuple are valid,
    # implying this pith to deeply satisfy this hint.
    return None
Ejemplo n.º 9
0
def _get_cause_or_none_sequence(sleuth: CauseSleuth) -> Optional[str]:
    '''
    Human-readable string describing the failure of the passed arbitrary object
    to satisfy the passed **PEP-compliant variadic sequence type hint** (i.e.,
    PEP-compliant type hint accepting one or more subscripted type hint
    arguments constraining *all* items of this object, which necessarily
    satisfies the :class:`collections.abc.Sequence` protocol with guaranteed
    ``O(1)`` indexation across all sequence items) if this object actually
    fails to satisfy this hint *or* ``None`` otherwise (i.e., if this object
    satisfies this hint).

    Parameters
    ----------
    sleuth : CauseSleuth
        Type-checking error cause sleuth.
    '''
    # Assert this type hint to describe a variadic sequence. See the parent
    # get_cause_or_none_sequence_standard() and get_cause_or_none_tuple()
    # functions for derivative logic.
    #
    # Note that this pith need *NOT* be validated to be an instance of the
    # expected variadic sequence, as the caller guarantees this to be the case.
    assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'
    assert (sleuth.hint_sign in HINT_PEP_SIGNS_SEQUENCE_STANDARD
            or (sleuth.hint_sign in HINT_PEP_SIGNS_TUPLE
                and len(sleuth.hint_childs) == 2
                and sleuth.hint_childs[1] is Ellipsis)), (
                    f'{repr(sleuth.hint)} neither '
                    f'standard sequence nor variadic tuple hint.')

    # If this sequence is non-empty...
    if sleuth.pith:
        # First child hint of this hint. All remaining child hints if any are
        # ignorable. Specifically, if this hint is:
        # * A standard sequence (e.g., "typing.List[str]"), this hint is
        #   subscripted by only one child hint.
        # * A variadic tuple (e.g., "typing.Tuple[str, ...]"), this hint is
        #   subscripted by only two child hints the latter of which is
        #   ignorable syntactic chuff.
        hint_child = sleuth.hint_childs[0]

        # If this child hint is *NOT* ignorable...
        if not is_hint_ignorable(hint_child):
            # Arbitrary iterator satisfying the enumerate() protocol, yielding
            # zero or more 2-tuples of the form "(item_index, item)", where:
            # * "item" is an arbitrary item of this sequence.
            # * "item_index" is the 0-based index of this item.
            pith_enumerator = None

            # If this sequence was indexed by the parent @beartype-generated
            # wrapper function by a pseudo-random integer in O(1) time,
            # type-check *ONLY* the same index of this sequence also in O(1)
            # time. Since the current call to that function failed a
            # type-check, either this index is the index responsible for that
            # failure *OR* this sequence is valid and another container
            # entirely is responsible for that failure. In either case, no
            # other indices of this sequence need be checked.
            if sleuth.random_int is not None:
                # 0-based index of this item calculated from this random
                # integer in the *SAME EXACT WAY* as in the parent
                # @beartype-generated wrapper function.
                pith_item_index = sleuth.random_int % len(sleuth.pith)

                # Pseudo-random item with this index in this sequence.
                pith_item = sleuth.pith[pith_item_index]

                # 2-tuple of this index and item in the same order as the
                # 2-tuples returned by the enumerate() builtin.
                pith_enumeratable = (pith_item_index, pith_item)

                # Iterator yielding only this 2-tuple.
                pith_enumerator = iter((pith_enumeratable, ))
                # print(f'Checking item {pith_item_index} in O(1) time!')
            # Else, this sequence was iterated by the parent
            # @beartype-generated wrapper function in O(n) time. In this case,
            # type-check *ALL* indices of this sequence in O(n) time as well.
            else:
                # Iterator yielding all indices and items of this sequence.
                pith_enumerator = enumerate(sleuth.pith)
                # print('Checking sequence in O(n) time!')

            # For each enumerated item of this (sub)sequence...
            for pith_item_index, pith_item in pith_enumerator:
                # Human-readable string describing the failure of this item
                # to satisfy this child hint if this item actually fails to
                # satisfy this child hint *or* "None" otherwise.
                pith_item_cause = sleuth.permute(
                    pith=pith_item, hint=hint_child).get_cause_or_none()

                # If this item is the cause of this failure, return a
                # substring describing this failure by embedding this
                # failure (itself intended to be embedded in a longer
                # string).
                if pith_item_cause is not None:
                    return (f'{sleuth.pith.__class__.__name__} item '
                            f'{pith_item_index} {pith_item_cause}')
                # Else, this item is *NOT* the cause of this failure.
                # Silently continue to the next.
        # Else, this child hint is ignorable.
    # Else, this sequence is empty, in which case all items of this sequence
    # (of which there are none) are valid. Just go with it, people.

    # Return "None", as all items of this sequence are valid, implying this
    # sequence to deeply satisfy this hint.
    return None