def is_interval(lower, upper, exclude_lower=False, include_upper=False):
    """Test that value is within the specified interval.

    The lower is the bottom of the interval, and upper is the top.  By
    default, the interval includes the lower bound and excludes the
    upper bound, for consistency with python ranges and lots of other
    things.  In mathematical notation, the default is: [lower, upper)

    The exclude_lower flag will exclude the lower bound, and the
    include_upper flag will include the upper bound.

    Note that since the implementation relies only on inequality
    comparisons which are implemented for all objects, by providing
    lower and upper of type str, the intervals can be defined as more
    lexicographical or otherwise, based on the object type.

    """
    # pylint: disable=unused-argument # args defined by test definition
    lowersym = '(' if exclude_lower else '['
    uppersym = ']' if include_upper else ')'
    intervalstr = '{0}{1}, {2}{3}'.format(
        lowersym, repr(lower), repr(upper), uppersym)
    if (lower > upper or (lower == upper and (
            exclude_lower or not include_upper))):
        raise ValueError('invalid interval "{0}"'.format(intervalstr))

    def is_interval_test(conf, path, value):
        if value < lower or (value <= lower and exclude_lower):
            return u'{0} is below the interval {1}'.format(
                repr(value), intervalstr)
        if value > upper or (value >= upper and not include_upper):
            return u'{0} is above the interval {1}'.format(
                repr(value), intervalstr)
        return None

    return is_interval_test