Source code for contracts.contract

# Copyright 2017 Benoit Bernard All Rights Reserved.

"""
Module containing code contracts.
"""

import re
import traceback
from collections import Iterable

# Regex used to extract the name of the variable passed as the first parameter to the
# contract function.
_MATCH_FIRST_PARAMETER_REGEX = re.compile(r"\(([\w.]+)[,)]")


[docs]def is_not_none(value): """ Checks that the specified value is not equal to None. :param value: the value to check. :raises: :class:`TypeError` if the value is equal to None. """ if value is None: raise TypeError("{0} was equal to None.".format(_get_parameter_name()))
[docs]def is_not_empty(value): """ Checks that the specified value is not empty. :param value: the value to check. To be considered empty, it must be equal to None, or equal to "" if it's a string. If it's an :class:`~collections.abc.Iterable` object, its length must be equal to 0. Any other object type will never be considered empty. :raises: :class:`ValueError` if the value is considered empty. """ if value is None or (type(value) is str and not value) or (isinstance(value, Iterable) and len(value) == 0): raise ValueError("{0} was empty.".format(_get_parameter_name()))
[docs]def is_equal_to_any(value, expected_values, expression_str=None): """ Checks that the specified value is equal to at least one of the expected values. :param value: the value to check. :param expected_values: an :class:`~collections.abc.Iterable` object containing the expected values. :param expression_str: (optional) a string representing the evaluated boolean expression (e.g. 'b > c'). :raises: :class:`ValueError` if the value is not equal to any of the expected values. """ if value not in expected_values: raise ValueError( "{0} with value {1} and type {2} was not equal to any of the expected values.".format( expression_str if expression_str else _get_parameter_name(), str(value), type(value).__name__))
[docs]def is_true(value, expression_str=None): """ Checks that the specified value is equal to True. :param value: the value to check. It can be a standard variable's value, or the result of an evaluated boolean expression (e.g. a or b > c). :param expression_str: (optional) a string representing the evaluated boolean expression (e.g. 'b > c'). :raises: :class:`TypeError` if the value is not True. """ if value is not True: raise ValueError("{0} was not True.".format(expression_str if expression_str else _get_parameter_name()))
[docs]def is_false(value, expression_str=None): """ Checks that the specified value is equal to False. :param value: the value to check. It can be a standard variable's value, or the result of an evaluated boolean expression (e.g. a or b > c). :param expression_str: (optional) a string representing the evaluated boolean expression (e.g. 'b > c'). :raises: :class:`TypeError` if the value is not False. """ if value is not False: raise ValueError("{0} was not False.".format(expression_str if expression_str else _get_parameter_name()))
[docs]def is_equal(value, expected_value, expression_str=None): """ Checks that the specified value is strictly equal to the expected value. :param value: the value to check. :param expected_value: the expected value. :param expression_str: (optional) a string representing the evaluated boolean expression (e.g. 'len(a) == 1'). :raises: :class:`ValueError` if the values are not strictly equal. """ if value != expected_value: raise ValueError( "{0} with value {1} was not equal to {2}.".format( expression_str if expression_str else _get_parameter_name(), value, expected_value))
[docs]def is_greater_than(value, expected_value): """ Checks that the specified value is greater than the expected value. :param value: the value to check. :param expected_value: the expected value. :raises: :class:`ValueError` if the value is not greater than the expected value. """ if value <= expected_value: raise ValueError( "{0} with value {1} was not greater than {2}.".format(_get_parameter_name(), value, expected_value))
[docs]def is_greater_than_or_equal(value, expected_value): """ Checks that the specified value is greater than or strictly equal to the expected value. :param value: the value to check. :param expected_value: the expected value. :raises: :class:`ValueError` if the value is not greater than or strictly equal to the expected value. """ if value < expected_value: raise ValueError("{0} with value {1} was not greater than or equal to {2}.".format(_get_parameter_name(), value, expected_value))
[docs]def all_have_attribute(value, attribute_name): """ Checks that all objects contained in value - be it a single object or an :class:`~collections.abc.Iterable` of objects - have the specified attribute. :param value: the value to check. It can be a single object, or an an :class:`~collections.abc.Iterable` of objects. :param attribute_name: a string containing the name of the attribute to look for. :raises: :class:`AttributeError` if one of the objects does not contain the specified attribute. """ if isinstance(value, Iterable): for item in value: if not hasattr(item, attribute_name): raise AttributeError("{0} contains an item of type {1} not having the expected attribute '{2}'.".format( _get_parameter_name(), type(item).__name__, attribute_name)) elif not hasattr(value, attribute_name): raise AttributeError( "{0} with type {1} does not have the expected attribute '{2}'.".format(_get_parameter_name(), type(value).__name__, attribute_name))
[docs]def all_have_method(value, method_name): """ Checks that all objects contained in value - be it a single object or an :class:`~collections.abc.Iterable` of objects - have the specified method. :param value: the value to check. It can be a single object, or an an :class:`~collections.abc.Iterable` of objects. :param method_name: a string containing the name of the method to look for. :raises: :class:`AttributeError` if one of the objects does not contain the specified method. """ if isinstance(value, Iterable): for item in value: if not (hasattr(item, method_name) and callable(getattr(item, method_name))): raise AttributeError("{0} contains an item of type {1} not having the expected method '{2}'.".format( _get_parameter_name(), type(item).__name__, method_name)) elif not (hasattr(value, method_name) and callable(getattr(value, method_name))): raise AttributeError("{0} with type {1} does not have the expected method '{2}'.".format(_get_parameter_name(), type(value).__name__, method_name))
[docs]def is_callable(value): """ Checks that the specified value is a callable object (i.e. has a '__call__' attribute). :param value: the value to check. :raises: :class:`TypeError` if the value is not a callable object. """ if not hasattr(value, '__call__'): raise TypeError("{0} with type {1} was not callable.".format(_get_parameter_name(), type(value).__name__))
[docs]def is_instance(value, cls): """ Checks that the specified value is an instance of the given class. :param value: the value to check. :param cls: the expected class of the value. :raises: :class:`TypeError` if the value is not an instance of the class. """ if not isinstance(value, cls): raise TypeError("{0} was not an instance of {1}.".format(_get_parameter_name(), cls.__name__))
def _get_parameter_name(): # Retrieve the line of code that performed the call to the contract function. stack = traceback.extract_stack() _, _, _, code = stack[-3] # 'code' will contain something like this: # 'contract.is_not_none(a, expression)' # # So we extract the name of the first variable ('a') to generate a more specific, meaningful error message. match = _MATCH_FIRST_PARAMETER_REGEX.search(code) if not match: raise Exception("The call to the code contract had an unexpected format.") return match.groups(0)[0]