Example #1
0
def _assert_invariant(contract: Contract, instance: Any) -> None:
    """Assert that the contract holds as a class invariant given the instance of the class."""
    if 'self' in contract.condition_arg_set:
        check = contract.condition(self=instance)
    else:
        check = contract.condition()

    if _not_check(check=check, contract=contract):
        if contract.error is not None and (inspect.ismethod(contract.error) or
                                           inspect.isfunction(contract.error)):
            assert contract.error_arg_set is not None, "Expected error_arg_set non-None if contract.error a function."
            assert contract.error_args is not None, "Expected error_args non-None if contract.error a function."

            if 'self' in contract.error_arg_set:
                raise contract.error(self=instance)
            else:
                raise contract.error()
        else:
            if 'self' in contract.condition_arg_set:
                msg = icontract._represent.generate_message(
                    contract=contract, condition_kwargs={"self": instance})
            else:
                msg = icontract._represent.generate_message(
                    contract=contract, condition_kwargs=dict())

            if contract.error is None:
                raise ViolationError(msg)
            elif isinstance(contract.error, type):
                raise contract.error(msg)
            else:
                raise NotImplementedError(
                    "Unhandled contract.error: {}".format(contract.error))
Example #2
0
def _assert_invariant(contract: Contract, instance: Any) -> None:
    """Assert that the contract holds as a class invariant given the instance of the class."""
    if 'self' in contract.condition_arg_set:
        check = contract.condition(self=instance)
    else:
        check = contract.condition()

    if not_check(check=check, contract=contract):
        raise _create_violation_error(contract=contract, resolved_kwargs={'self': instance})
Example #3
0
def _assert_precondition(contract: Contract,
                         resolved_kwargs: Mapping[str, Any]) -> None:
    """
    Assert that the contract holds as a precondition.

    :param contract: contract to be verified
    :param resolved_kwargs:
        resolved keyword arguments of the call (including the default argument values of the decorated function)
    :return:
    """
    condition_kwargs = select_condition_kwargs(contract=contract,
                                               resolved_kwargs=resolved_kwargs)

    check = contract.condition(**condition_kwargs)

    if not_check(check=check, contract=contract):
        if contract.error is not None and (inspect.ismethod(contract.error) or
                                           inspect.isfunction(contract.error)):
            assert contract.error_arg_set is not None, "Expected error_arg_set non-None if contract.error a function."
            assert contract.error_args is not None, "Expected error_args non-None if contract.error a function."

            error_kwargs = {
                arg_name: value
                for arg_name, value in resolved_kwargs.items()
                if arg_name in contract.error_arg_set
            }

            missing_args = [
                arg_name for arg_name in contract.error_args
                if arg_name not in resolved_kwargs
            ]
            if missing_args:
                msg_parts = []
                if contract.location is not None:
                    msg_parts.append("{}:\n".format(contract.location))

                msg_parts.append((
                    "The argument(s) of the precondition error have not been set: {}. "
                    "Does the original function define them? Did you supply them in the call?"
                ).format(missing_args))

                raise TypeError(''.join(msg_parts))

            raise contract.error(**error_kwargs)

        else:
            msg = icontract._represent.generate_message(
                contract=contract, condition_kwargs=condition_kwargs)
            if contract.error is None:
                raise ViolationError(msg)
            elif isinstance(contract.error, type):
                raise contract.error(msg)
Example #4
0
def _create_violation_error(contract: Contract, resolved_kwargs: Mapping[str, Any]) -> BaseException:
    """Create the violation error based on the violated contract."""
    exception = None  # type: Optional[BaseException]

    if contract.error is None:
        try:
            msg = icontract._represent.generate_message(contract=contract, resolved_kwargs=resolved_kwargs)
        except Exception as err:
            parts = ["Failed to recompute the values of the contract condition:\n"]
            if contract.location is not None:
                parts.append("{}:\n".format(contract.location))

            if contract.description is not None:
                parts.append("{}: ".format(contract.description))

            parts.append(icontract._represent.represent_condition(condition=contract.condition))

            raise RuntimeError(''.join(parts)) from err

        exception = ViolationError(msg)
    elif inspect.ismethod(contract.error) or inspect.isfunction(contract.error):
        assert contract.error_arg_set is not None, ("Expected error_arg_set non-None if contract.error a function.")
        assert contract.error_args is not None, ("Expected error_args non-None if contract.error a function.")

        error_kwargs = select_error_kwargs(contract=contract, resolved_kwargs=resolved_kwargs)

        exception = cast(BaseException, contract.error(**error_kwargs))  # type: ignore

        if not isinstance(exception, BaseException):
            raise TypeError(
                "The exception returned by the contract's error {} does not inherit from BaseException.".format(
                    contract.error))
    elif isinstance(contract.error, type):
        if not issubclass(contract.error, BaseException):
            raise TypeError(
                "The exception class supplied in the contract's error {} is not a subclass of BaseException.".format(
                    contract.error))

        msg = icontract._represent.generate_message(contract=contract, resolved_kwargs=resolved_kwargs)
        exception = contract.error(msg)
    elif isinstance(contract.error, BaseException):
        exception = contract.error
    else:
        raise NotImplementedError(
            ("icontract does not know how to handle the error of type {} "
             "(expected a function, a subclass of BaseException or an instance of BaseException)").format(
                 type(contract.error)))

    assert exception is not None
    return exception
Example #5
0
    def __init__(
            self,
            condition: Callable[..., Any],
            description: Optional[str] = None,
            a_repr: reprlib.Repr = icontract._globals.aRepr,
            enabled: bool = __debug__,
            error: Optional[Union[Callable[..., Exception],
                                  type]] = None) -> None:
        """
        Initialize a class decorator to establish the invariant on all the public methods.

        :param condition: invariant predicate
        :param description: textual description of the invariant
        :param a_repr: representation instance that defines how the values are represented
        :param enabled:
                The decorator is applied only if this argument is set.

                Otherwise, the condition check is disabled and there is no run-time overhead.

                The default is to always check the condition unless the interpreter runs in optimized mode (``-O`` or
                ``-OO``).
        :param error:
            if given as a callable, ``error`` is expected to accept a subset of function arguments
            (*e.g.*, also including ``result`` for perconditions, only ``self`` for invariants *etc.*) and return
            an exception. The ``error`` is called on contract violation and the resulting exception is raised.

            Otherwise, it is expected to denote an Exception class which is instantiated with the violation message
            and raised on contract violation.
        :return:

        """
        # pylint: disable=too-many-arguments
        self.enabled = enabled
        self._contract = None  # type: Optional[Contract]

        if not enabled:
            return

        location = None  # type: Optional[str]
        tb_stack = traceback.extract_stack(limit=2)[:1]
        if len(tb_stack) > 0:
            frame = tb_stack[0]
            location = 'File {}, line {} in {}'.format(frame.filename,
                                                       frame.lineno,
                                                       frame.name)

        self._contract = Contract(condition=condition,
                                  description=description,
                                  a_repr=a_repr,
                                  error=error,
                                  location=location)

        if self._contract.mandatory_args and self._contract.mandatory_args != [
                'self'
        ]:
            raise ValueError(
                "Expected an invariant condition with at most an argument 'self', but got: {}"
                .format(self._contract.condition_args))
Example #6
0
    def __init__(
            self,
            condition: Callable[..., Any],
            description: Optional[str] = None,
            a_repr: reprlib.Repr = icontract._globals.aRepr,
            enabled: bool = __debug__,
            error: Optional[Union[Callable[..., Exception],
                                  type]] = None) -> None:
        """
        Initialize.

        :param condition: precondition predicate
        :param description: textual description of the precondition
        :param a_repr: representation instance that defines how the values are represented
        :param enabled:
            The decorator is applied only if this argument is set.

            Otherwise, the condition check is disabled and there is no run-time overhead.

            The default is to always check the condition unless the interpreter runs in optimized mode (``-O`` or
            ``-OO``).
        :param error:
            if given as a callable, ``error`` is expected to accept a subset of function arguments
            (*e.g.*, also including ``result`` for perconditions, only ``self`` for invariants *etc.*) and return
            an exception. The ``error`` is called on contract violation and the resulting exception is raised.

            Otherwise, it is expected to denote an Exception class which is instantiated with the violation message
            and raised on contract violation.

        """
        # pylint: disable=too-many-arguments
        self.enabled = enabled
        self._contract = None  # type: Optional[Contract]

        if not enabled:
            return

        self._contract = Contract(condition=condition,
                                  description=description,
                                  a_repr=a_repr,
                                  error=error)
Example #7
0
    def __init__(
        self,
        condition: Callable[..., Any],
        description: Optional[str] = None,
        a_repr: reprlib.Repr = icontract._globals.aRepr,
        enabled: bool = __debug__,
        error: Optional[Union[Callable[..., ExceptionT], Type[ExceptionT],
                              BaseException]] = None
    ) -> None:
        """
        Initialize a class decorator to establish the invariant on all the public methods.

        :param condition:
            invariant predicate.

            The condition must not be a coroutine function as dunder functions (including ``__init__``)
            of a class can not be async.
        :param description: textual description of the invariant
        :param a_repr: representation instance that defines how the values are represented
        :param enabled:
                The decorator is applied only if this argument is set.

                Otherwise, the condition check is disabled and there is no run-time overhead.

                The default is to always check the condition unless the interpreter runs in optimized mode (``-O`` or
                ``-OO``).
        :param error:
            The error is expected to denote either:

            * A callable. ``error`` is expected to accept a subset of function arguments and return an exception.
              The ``error`` is called on contract violation and the resulting exception is raised.
            * A subclass of ``BaseException`` which is instantiated with the violation message and raised
              on contract violation.
            * An instance of ``BaseException`` that will be raised with the traceback on contract violation.
        :return:

        """
        # pylint: disable=too-many-arguments
        self.enabled = enabled
        self._contract = None  # type: Optional[Contract]

        if not enabled:
            return

        if error is None:
            pass
        elif isinstance(error, type):
            if not issubclass(error, BaseException):
                raise ValueError(
                    ("The error of the contract is given as a type, "
                     "but the type does not inherit from BaseException: {}"
                     ).format(error))
        else:
            if not inspect.isfunction(error) and not inspect.ismethod(
                    error) and not isinstance(error, BaseException):
                raise ValueError((
                    "The error of the contract must be either a callable (a function or a method), "
                    "a class (subclass of BaseException) or an instance of BaseException, but got: {}"
                ).format(error))

        location = None  # type: Optional[str]
        tb_stack = traceback.extract_stack(limit=2)[:1]
        if len(tb_stack) > 0:
            frame = tb_stack[0]
            location = 'File {}, line {} in {}'.format(frame.filename,
                                                       frame.lineno,
                                                       frame.name)

        if inspect.iscoroutinefunction(condition):
            raise ValueError(
                "Async conditions are not possible in invariants as sync methods such as __init__ have to be wrapped."
            )

        self._contract = Contract(condition=condition,
                                  description=description,
                                  a_repr=a_repr,
                                  error=error,
                                  location=location)

        if self._contract.mandatory_args and self._contract.mandatory_args != [
                'self'
        ]:
            raise ValueError(
                "Expected an invariant condition with at most an argument 'self', but got: {}"
                .format(self._contract.condition_args))
Example #8
0
    def __init__(
        self,
        condition: Callable[..., Any],
        description: Optional[str] = None,
        a_repr: reprlib.Repr = icontract._globals.aRepr,
        enabled: bool = __debug__,
        error: Optional[Union[Callable[..., ExceptionT], Type[ExceptionT],
                              BaseException]] = None
    ) -> None:
        """
        Initialize.

        :param condition: precondition predicate

            If the condition returns a coroutine, you must specify the `error` as
            coroutines have side effects and can not be recomputed.
        :param description: textual description of the precondition
        :param a_repr: representation instance that defines how the values are represented
        :param enabled:
            The decorator is applied only if this argument is set.

            Otherwise, the condition check is disabled and there is no run-time overhead.

            The default is to always check the condition unless the interpreter runs in optimized mode (``-O`` or
            ``-OO``).
        :param error:
            The error is expected to denote either:

            * A callable. ``error`` is expected to accept a subset of function arguments and return an exception.
              The ``error`` is called on contract violation and the resulting exception is raised.
            * A subclass of ``BaseException`` which is instantiated with the violation message and raised
              on contract violation.
            * An instance of ``BaseException`` that will be raised with the traceback on contract violation.

        """
        # pylint: disable=too-many-arguments
        self.enabled = enabled
        self._contract = None  # type: Optional[Contract]

        if not enabled:
            return

        if error is None:
            pass
        elif isinstance(error, type):
            if not issubclass(error, BaseException):
                raise ValueError(
                    ("The error of the contract is given as a type, "
                     "but the type does not inherit from BaseException: {}"
                     ).format(error))
        else:
            if not inspect.isfunction(error) and not inspect.ismethod(
                    error) and not isinstance(error, BaseException):
                raise ValueError((
                    "The error of the contract must be either a callable (a function or a method), "
                    "a class (subclass of BaseException) or an instance of BaseException, but got: {}"
                ).format(error))

        location = None  # type: Optional[str]
        tb_stack = traceback.extract_stack(limit=2)[:1]
        if len(tb_stack) > 0:
            frame = tb_stack[0]
            location = 'File {}, line {} in {}'.format(frame.filename,
                                                       frame.lineno,
                                                       frame.name)

        self._contract = Contract(condition=condition,
                                  description=description,
                                  a_repr=a_repr,
                                  error=error,
                                  location=location)
Example #9
0
def _assert_postcondition(contract: Contract,
                          resolved_kwargs: Mapping[str, Any]) -> None:
    """
    Assert that the contract holds as a postcondition.

    The arguments to the postcondition are given as ``resolved_kwargs`` which includes
    both argument values captured in snapshots and actual argument values and the result of a function.

    :param contract: contract to be verified
    :param resolved_kwargs: resolved keyword arguments (including the default values, ``result`` and ``OLD``)
    :return:
    """
    assert 'result' in resolved_kwargs, \
        "Expected 'result' to be set in the resolved keyword arguments of a postcondition."

    # Check that all arguments to the condition function have been set.
    missing_args = [
        arg_name for arg_name in contract.mandatory_args
        if arg_name not in resolved_kwargs
    ]
    if missing_args:
        msg_parts = []  # type: List[str]
        if contract.location is not None:
            msg_parts.append("{}:\n".format(contract.location))

        msg_parts.append((
            "The argument(s) of the postcondition have not been set: {}. "
            "Does the original function define them? Did you supply them in the call?"
        ).format(missing_args))

        raise TypeError(''.join(msg_parts))

    condition_kwargs = {
        arg_name: value
        for arg_name, value in resolved_kwargs.items()
        if arg_name in contract.condition_arg_set
    }

    check = contract.condition(**condition_kwargs)

    if _not_check(check=check, contract=contract):
        if contract.error is not None and (inspect.ismethod(contract.error) or
                                           inspect.isfunction(contract.error)):
            assert contract.error_arg_set is not None, "Expected error_arg_set non-None if contract.error a function."
            assert contract.error_args is not None, "Expected error_args non-None if contract.error a function."

            error_kwargs = {
                arg_name: value
                for arg_name, value in resolved_kwargs.items()
                if arg_name in contract.error_arg_set
            }

            missing_args = [
                arg_name for arg_name in contract.error_args
                if arg_name not in resolved_kwargs
            ]
            if missing_args:
                msg_parts = []
                if contract.location is not None:
                    msg_parts.append("{}:\n".format(contract.location))

                msg_parts.append((
                    "The argument(s) of the postcondition error have not been set: {}. "
                    "Does the original function define them? Did you supply them in the call?"
                ).format(missing_args))

                raise TypeError(''.join(msg_parts))

            raise contract.error(**error_kwargs)

        else:
            msg = icontract._represent.generate_message(
                contract=contract, condition_kwargs=condition_kwargs)
            if contract.error is None:
                raise ViolationError(msg)
            elif isinstance(contract.error, type):
                raise contract.error(msg)