예제 #1
0
    def _die_if_slice_len_ne_value_len(self, index, value) -> None:
        '''
        Raise an exception only if the passed parameters when passed to the
        parent :meth:`__setitem__` dunder method signify an external attempt to
        change the length of this fixed length with slicing.

        This function is intended to be called by the :meth:`__setitem__`
        dunder method to validate the passed parameters.

        Parameters
        ----------
        index
            0-based index, slice object, or tuple of 0-based indices and slice
            objects to index this fixed list with.
        value
            Object to set this index(s) of this fixed list to.

        Raises
        ----------
        _BeartypeUtilCachedFixedListException
            If this index is a **slice object** (i.e., :class:`slice` instance
            underlying slice syntax) and this value is either:

            * **Unsized** (i.e., unsupported by the :func:`len` builtin).
            * Sized but has a length differing from that of this fixed list.
        '''

        # If this index is *NOT* a slice, silently reduce to a noop.
        if not isinstance(index, slice):
            return
        # Else, this index is a slice.

        # If this value is *NOT* a sized container, raise an exception.
        if not isinstance(value, Sized):
            raise _BeartypeUtilCachedFixedListException(
                '{} slice {!r} not settable to unsized {}.'.format(
                    self._label, index, get_object_representation(value)))
        # Else, this value is a sized container.

        # 0-based first and one-past-the-last indices sliced by this slice.
        start, stop_plus_one, _ = index.indices(len(self))

        # Number of items of this fixed list sliced by this slice. By
        # definition, this is guaranteed to be a non-negative integer.
        slice_len = stop_plus_one - start

        # Number of items of this sized container to set this slice to.
        value_len = len(value)

        # If these two lengths differ, raise an exception.
        if slice_len != value_len:
            raise _BeartypeUtilCachedFixedListException(
                '{} slice {!r} of length {} not settable to '
                '{} of differing length {}.'.format(
                    self._label,
                    index,
                    slice_len,
                    get_object_representation(value),
                    value_len,
                ))
예제 #2
0
def get_cause_object_not_nonpep_tuple(pith: object, hint: Tuple[type,
                                                                ...]) -> str:
    '''
    Human-readable error message describing the failure of the passed arbitrary
    object to satisfy (i.e., be an instance of at least one of the items of)
    the passed PEP-noncompliant tuple of classes.

    Parameters
    ----------
    pith : object
        Object to be described as *not* an instance of at least of the items of
        this tuple.
    hint : Tuple[type]
        Tuple of classes to describe this object as *not* being an instance of.

    Returns
    ----------
    str
        Human-readable error message describing this failure.

    Raises
    ----------
    _BeartypeCallHintRaiseException
        If either:

        * This tuple is *not* actually a tuple.
        * This tuple contains one or more non-classes.
        * This object is actually an instance of at least one items of this
          tuple.
    '''

    # If this hint is *NOT* a PEP-noncompliant tuple of classes, raise an
    # exception.
    die_unless_hint_nonpep_tuple(
        hint=hint,
        is_str_valid=False,
        exception_cls=_BeartypeCallHintRaiseException,
    )
    # Else, this hint is a PEP-noncompliant tuple of classes.

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

    # If this pith is an instance of one or more of the classes listed by this
    # tuple, raise an exception.
    if isinstance(pith, hint):
        raise _BeartypeCallHintRaiseException(
            f'{pith_repr} is instance of one or more classes in {repr(hint)}.')
    # Else, this pith is *NOT* an instance of one or more of the classes listed
    # by this tuple.

    # Return a substring describing this failure intended to be embedded in a
    # longer string.
    return (
        f'value {pith_repr} not {join_delimited_disjunction_classes(hint)}')
예제 #3
0
    def _label(self) -> str:
        '''
        Human-readable representation of this fixed list trimmed to a
        reasonable length.

        This string property is intended to be interpolated into exception
        messages and should probably *not* be called in contexts where
        efficiency is a valid concern.
        '''

        # One-liners for magnanimous pusillanimousness.
        return 'Fixed list ' + get_object_representation(self)
예제 #4
0
def get_cause_object_not_type(pith: object, hint: type) -> str:
    '''
    Human-readable error message describing the failure of the passed arbitrary
    object to satisfy (i.e., be an instance of) the passed class.

    Parameters
    ----------
    pith : object
        Object to be described as *not* an instance of this class.
    hint : type
        Class to describe this object as *not* being an instance of.

    Returns
    ----------
    str
        Human-readable error message describing this failure.

    Raises
    ----------
    _BeartypeCallHintRaiseException
        If either:

        * This class is *not* actually a class.
        * This object is actually an instance of this class.
    '''

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

    # If this hint is *NOT* a class, raise an exception.
    if not isinstance(hint, type):
        raise _BeartypeCallHintRaiseException(
            f'Non-PEP type hint {repr(hint)} not class.')
    # Else, this hint is a class.
    #
    # If this pith is an instance of this class, raise an exception.
    elif isinstance(pith, hint):
        raise _BeartypeCallHintRaiseException(
            f'{pith_repr} is instance of {repr(hint)}.')
    # Else, this pith is *NOT* an instance of this type.

    # Return a substring describing this failure intended to be embedded in a
    # longer string.
    return f'value {pith_repr} not {label_class(hint)}'
예제 #5
0
 def extend(self, obj):
     raise _BeartypeUtilCachedFixedListException(
         '{} not extendable by {}.'.format(self._label,
                                           get_object_representation(obj)))
예제 #6
0
 def __imul__(self, value):
     raise _BeartypeUtilCachedFixedListException(
         '{} not multipliable by {}.'.format(
             self._label, get_object_representation(value)))
예제 #7
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))
예제 #8
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}'
    )
예제 #9
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