diff --git a/tests/test__extremes.py b/tests/test__extremes.py new file mode 100644 index 00000000..6b98ffc4 --- /dev/null +++ b/tests/test__extremes.py @@ -0,0 +1,57 @@ +"""Test Extremes.""" +# -*- coding: utf-8 -*- + +# standard +from typing import Any + +# external +import pytest + +# project +from validators._extremes import AbsMax, AbsMin + +abs_max = AbsMax() +abs_min = AbsMin() + + +@pytest.mark.parametrize( + ("value",), + [(None,), ("",), (12,), (abs_min,)], +) +def test_abs_max_is_greater_than_every_other_value(value: Any): + """Test if AbsMax is greater than every other value.""" + assert value < abs_max + assert abs_max > value + + +def test_abs_max_is_not_greater_than_itself(): + """Test if AbsMax is not greater than itself.""" + assert not (abs_max > abs_max) + + +def test_other_comparison_methods_for_abs_max(): + """Test other comparison methods for AbsMax.""" + assert abs_max <= abs_max + assert abs_max == abs_max + assert abs_max == abs_max + + +@pytest.mark.parametrize( + ("value",), + [(None,), ("",), (12,), (abs_max,)], +) +def test_abs_min_is_smaller_than_every_other_value(value: Any): + """Test if AbsMin is less than every other value.""" + assert value > abs_min + + +def test_abs_min_is_not_greater_than_itself(): + """Test if AbsMin is not less than itself.""" + assert not (abs_min < abs_min) + + +def test_other_comparison_methods_for_abs_min(): + """Test other comparison methods for AbsMin.""" + assert abs_min <= abs_min + assert abs_min == abs_min + assert abs_min == abs_min diff --git a/tests/test_between.py b/tests/test_between.py index 45f0eeeb..962ce23a 100644 --- a/tests/test_between.py +++ b/tests/test_between.py @@ -1,33 +1,59 @@ +"""Test Between.""" # -*- coding: utf-8 -*- + +# standard +from datetime import datetime +from typing import TypeVar + +# external import pytest -import validators +# project +from validators import between, ValidationFailure + +T = TypeVar("T", int, float, str, datetime) -@pytest.mark.parametrize(('value', 'min', 'max'), [ - (12, 11, 13), - (12, None, 14), - (12, 11, None), - (12, 12, 12) -]) -def test_returns_true_on_valid_range(value, min, max): - assert validators.between(value, min=min, max=max) +@pytest.mark.parametrize( + ("value", "min_val", "max_val"), + [(12, 11, 13), (12, None, 14), (12, 11, None), (12, 12, 12)], +) +def test_returns_true_on_valid_range(value: T, min_val: T, max_val: T) -> None: + """Test returns true on valid range.""" + assert between(value, min_val=min_val, max_val=max_val) -@pytest.mark.parametrize(('value', 'min', 'max'), [ - (12, 13, 12), - (12, None, None), -]) -def test_raises_assertion_error_for_invalid_args(value, min, max): + +@pytest.mark.parametrize( + ("value", "min_val", "max_val"), + [(12, 13, 12), (12, None, None)], +) +def test_raises_assertion_error_for_invalid_args(value: T, min_val: T, max_val: T) -> None: + """Test raises assertion error for invalid args.""" with pytest.raises(AssertionError): - assert validators.between(value, min=min, max=max) + assert between(value, min_val=min_val, max_val=max_val) + + +@pytest.mark.parametrize( + ("value", "min_val", "max_val"), + [ + (12, "13.5", datetime(1970, 1, 1)), + ("12", 20.5, "None"), + (datetime(1970, 1, 1), 20, "string"), + (30, 40, "string"), + ], +) +def test_raises_type_error_for_invalid_args(value: T, min_val: T, max_val: T) -> None: + """Test raises type error for invalid args.""" + with pytest.raises(TypeError): + assert between(value, min_val=min_val, max_val=max_val) -@pytest.mark.parametrize(('value', 'min', 'max'), [ - (12, 13, 14), - (12, None, 11), - (12, 13, None) -]) -def test_returns_failed_validation_on_invalid_range(value, min, max): - result = validators.between(value, min=min, max=max) - assert isinstance(result, validators.ValidationFailure) +@pytest.mark.parametrize( + ("value", "min_val", "max_val"), + [(12, 13, 14), (12, None, 11), (12, 13, None)], +) +def test_returns_failed_validation_on_invalid_range(value: T, min_val: T, max_val: T) -> None: + """Test returns failed validation on invalid range.""" + result = between(value, min_val=min_val, max_val=max_val) + assert isinstance(result, ValidationFailure) diff --git a/tests/test_extremes.py b/tests/test_extremes.py deleted file mode 100644 index d9f5023c..00000000 --- a/tests/test_extremes.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest - -from validators import Max, Min - - -@pytest.mark.parametrize(('value',), [ - (None,), - ('',), - (12,), - (Min,), -]) -def test_max_is_greater_than_every_other_value(value): - assert value < Max - assert Max > value - - -def test_max_is_not_greater_than_itself(): - assert not (Max < Max) - - -def test_other_comparison_methods_for_max(): - assert Max <= Max - assert Max == Max - assert not (Max != Max) - - -@pytest.mark.parametrize(('value',), [ - (None,), - ('',), - (12,), - (Max,), -]) -def test_min_is_smaller_than_every_other_value(value): - assert value > Min - - -def test_min_is_not_greater_than_itself(): - assert not (Min < Min) - - -def test_other_comparison_methods_for_min(): - assert Min <= Min - assert Min == Min - assert not (Min != Min) diff --git a/tests/test_length.py b/tests/test_length.py index 86342f1e..3c389a36 100644 --- a/tests/test_length.py +++ b/tests/test_length.py @@ -1,37 +1,36 @@ +"""Test Length.""" # -*- coding: utf-8 -*- + +# external import pytest -import validators +# project +from validators import length, ValidationFailure -@pytest.mark.parametrize(('value', 'min', 'max'), [ - ('password', 2, 10), - ('password', None, 10), - ('password', 2, None), - ('password', 8, 8) -]) -def test_returns_true_on_valid_length(value, min, max): - assert validators.length(value, min=min, max=max) +@pytest.mark.parametrize( + ("value", "min_val", "max_val"), + [("password", 2, 10), ("password", 0, 10), ("password", 8, 8)], +) +def test_returns_true_on_valid_length(value: str, min_val: int, max_val: int): + """Test returns true on valid length.""" + assert length(value, min_val=min_val, max_val=max_val) -@pytest.mark.parametrize(('value', 'min', 'max'), [ - ('something', 13, 12), - ('something', -1, None), - ('something', -1, None), - ('something', -3, -2) -]) -def test_raises_assertion_error_for_invalid_args(value, min, max): +@pytest.mark.parametrize( + ("value", "min_val", "max_val"), + [("something", 14, 12), ("something", -10, -20), ("something", 0, -2)], +) +def test_raises_assertion_error_for_invalid_args(value: str, min_val: int, max_val: int): + """Test raises assertion error for invalid args.""" with pytest.raises(AssertionError): - assert validators.length(value, min=min, max=max) - - -@pytest.mark.parametrize(('value', 'min', 'max'), [ - ('something', 13, 14), - ('something', None, 6), - ('something', 13, None) -]) -def test_returns_failed_validation_on_invalid_range(value, min, max): - assert isinstance( - validators.length(value, min=min, max=max), - validators.ValidationFailure - ) + assert length(value, min_val=min_val, max_val=max_val) + + +@pytest.mark.parametrize( + ("value", "min_val", "max_val"), + [("something", 13, 14), ("something", 0, 6), ("something", 14, 20)], +) +def test_returns_failed_validation_on_invalid_range(value: str, min_val: int, max_val: int): + """Test returns failed validation on invalid range.""" + assert isinstance(length(value, min_val=min_val, max_val=max_val), ValidationFailure) diff --git a/tests/test_validation_failure.py b/tests/test_validation_failure.py index f8dc2e2b..75835eb7 100644 --- a/tests/test_validation_failure.py +++ b/tests/test_validation_failure.py @@ -1,25 +1,35 @@ -import validators +"""Test validation Failure.""" +# -*- coding: utf-8 -*- -obj_repr = ( - "ValidationFailure(func=between" -) +# project +from validators import between -class TestValidationFailure(object): - def setup_method(self, method): - self.obj = validators.between(3, min=4, max=5) +failed_obj_repr = "ValidationFailure(func=between" + + +class TestValidationFailure: + """Test validation Failure.""" + + def setup_method(self): + """Setup Method.""" + self.is_in_between = between(3, min_val=4, max_val=5) def test_boolean_coerce(self): - assert not bool(self.obj) - assert not self.obj + """Test Boolean.""" + assert not bool(self.is_in_between) + assert not self.is_in_between def test_repr(self): - assert obj_repr in repr(self.obj) + """Test Repr.""" + assert failed_obj_repr in repr(self.is_in_between) - def test_unicode(self): - assert obj_repr in str(self.obj) + def test_string(self): + """Test Repr.""" + assert failed_obj_repr in str(self.is_in_between) def test_arguments_as_properties(self): - assert self.obj.value == 3 - assert self.obj.min == 4 - assert self.obj.max == 5 + """Test argument properties.""" + assert self.is_in_between.__dict__["value"] == 3 + assert self.is_in_between.__dict__["min_val"] == 4 + assert self.is_in_between.__dict__["max_val"] == 5 diff --git a/validators/__init__.py b/validators/__init__.py index f623e12f..d3a0b468 100644 --- a/validators/__init__.py +++ b/validators/__init__.py @@ -1,18 +1,12 @@ +"""Validate Anything!""" +# -*- coding: utf-8 -*- +# from ._extremes import AbsMax, AbsMin + from .between import between from .btc_address import btc_address -from .card import ( - amex, - card_number, - diners, - discover, - jcb, - mastercard, - unionpay, - visa -) +from .card import amex, card_number, diners, discover, jcb, mastercard, unionpay, visa from .domain import domain from .email import email -from .extremes import Max, Min from .hashes import md5, sha1, sha224, sha256, sha512 from .i18n import fi_business_id, fi_ssn from .iban import iban @@ -25,11 +19,41 @@ from .utils import ValidationFailure, validator from .uuid import uuid -__all__ = ('between', 'domain', 'email', 'Max', 'Min', 'md5', 'sha1', 'sha224', - 'sha256', 'sha512', 'fi_business_id', 'fi_ssn', 'iban', 'ipv4', - 'ipv4_cidr', 'ipv6', 'ipv6_cidr', 'length', 'mac_address', 'slug', - 'truthy', 'url', 'ValidationFailure', 'validator', 'uuid', - 'card_number', 'visa', 'mastercard', 'amex', 'unionpay', 'diners', - 'jcb', 'discover', 'btc_address') +__all__ = ( + "amex", + "between", + "btc_address", + "card_number", + "diners", + "discover", + "domain", + "email", + "fi_business_id", + "fi_ssn", + "iban", + "ipv4_cidr", + "ipv4", + "ipv6_cidr", + "ipv6", + "jcb", + "length", + "mac_address", + "mastercard", + # "AbsMax", + "md5", + # "AbsMax", + "sha1", + "sha224", + "sha256", + "sha512", + "slug", + "truthy", + "unionpay", + "url", + "uuid", + "ValidationFailure", + "validator", + "visa", +) -__version__ = '0.20.0' +__version__ = "0.20.0" diff --git a/validators/_extremes.py b/validators/_extremes.py new file mode 100644 index 00000000..26a522e2 --- /dev/null +++ b/validators/_extremes.py @@ -0,0 +1,60 @@ +"""Extremes.""" +# -*- coding: utf-8 -*- + +# standard +from functools import total_ordering +from typing import Any + + +@total_ordering +class AbsMax: + """An object that is greater than any other object (except itself). + + Inspired by https://pypi.python.org/pypi/Extremes + + Examples:: + + >>> import sys + + >>> AbsMax > AbsMin + True + + >>> AbsMax > sys.maxint + True + + >>> AbsMax > 99999999999999999 + True + + .. versionadded:: 0.2 + """ + + def __ge__(self, other: Any) -> bool: + """GreaterThanOrEqual.""" + return other is not AbsMax + + +@total_ordering +class AbsMin: + """An object that is less than any other object (except itself). + + Inspired by https://pypi.python.org/pypi/Extremes + + Examples:: + + >>> import sys + + >>> AbsMin < -sys.maxint + True + + >>> AbsMin < None + True + + >>> AbsMin < '' + True + + .. versionadded:: 0.2 + """ + + def __le__(self, other: Any) -> bool: + """LessThanOrEqual.""" + return other is not AbsMin diff --git a/validators/between.py b/validators/between.py index 46f223c9..db674e8d 100644 --- a/validators/between.py +++ b/validators/between.py @@ -1,61 +1,103 @@ -from .extremes import Max, Min +"""Between.""" +# -*- coding: utf-8 -*- + +# standard +from typing import TypeVar, Union +from datetime import datetime + +# project +from ._extremes import AbsMax, AbsMin from .utils import validator +T = TypeVar("T", int, float, str, datetime) + @validator -def between(value, min=None, max=None): - """ - Validate that a number is between minimum and/or maximum value. +def between( + value: T, + /, + *, + min_val: Union[T, AbsMin, None] = None, + max_val: Union[T, AbsMax, None] = None, +) -> bool: + """Validate that a number is between minimum and/or maximum value. This will work with any comparable type, such as floats, decimals and dates - not just integers. + not just integers. This validator is originally based on `WTForms-NumberRange-Validator`_ - This validator is originally based on `WTForms NumberRange validator`_. - - .. _WTForms NumberRange validator: - https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py + .. _WTForms-NumberRange-Validator: + https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py#L166-L220 Examples:: >>> from datetime import datetime - >>> between(5, min=2) - True + >>> between(5, min_val=2) + # Output: True - >>> between(13.2, min=13, max=14) - True + >>> between(13.2, min_val=13, max_val=14) + # Output: True - >>> between(500, max=400) - ValidationFailure(func=between, args=...) + >>> between(500, max_val=400) + # Output: ValidationFailure(func=between, args=...) >>> between( ... datetime(2000, 11, 11), - ... min=datetime(1999, 11, 11) + ... min_val=datetime(1999, 11, 11) ... ) - True + # True + + Args: + `value`: + [Required] Value which is to be compared. + `min_val`: + [Optional] The minimum required value of the number. + If not provided, minimum value will not be checked. + `max_val`: + [Optional] The maximum value of the number. + If not provided, maximum value will not be checked. + Either one of `min_val` or `max_val` must be provided. + + Returns: + A `boolean` if `value` is greater than `min_val` and + less than `max_val`. - :param min: - The minimum required value of the number. If not provided, minimum - value will not be checked. - :param max: - The maximum value of the number. If not provided, maximum value - will not be checked. + Raises: + `AssertionError`: + - If both `min_val` and `max_val` are `None`. + - If `min_val` is greater than `max_val`. + + `TypeError`: + - If there's a type mismatch before comparison .. versionadded:: 0.2 """ - if min is None and max is None: - raise AssertionError( - 'At least one of `min` or `max` must be specified.' - ) - if min is None: - min = Min - if max is None: - max = Max - try: - min_gt_max = min > max - except TypeError: - min_gt_max = max < min - if min_gt_max: - raise AssertionError('`min` cannot be more than `max`.') - - return min <= value and max >= value + if min_val is None and max_val is None: + raise AssertionError("At least one of either `min_val` or `max_val` must be specified") + + if max_val is None: + max_val = AbsMax() + if min_val is None: + min_val = AbsMin() + + # if isinstance(min_val, AbsMin) and isinstance(max_val, AbsMax): + # return min_val <= value <= max_val + + if isinstance(min_val, AbsMin): + if type(value) is not type(max_val): + raise TypeError("`value` and `max_val` must be of same type") + return min_val <= value <= max_val + + if isinstance(max_val, AbsMax): + if type(value) is not type(min_val): + raise TypeError("`value` and `min_val` must be of same type") + return min_val <= value <= max_val + + if type(min_val) is type(max_val): + if min_val > max_val: + raise AssertionError("`min_val` cannot be more than `max_val`") + if type(value) is not type(min_val): # or type(max_val): + raise TypeError("`value` and (`min_val` or `max_val`) must be of same type") + return min_val <= value <= max_val + + raise TypeError("`value` and `min_val` and `max_val` must be of same type") diff --git a/validators/extremes.py b/validators/extremes.py deleted file mode 100644 index 43d168a7..00000000 --- a/validators/extremes.py +++ /dev/null @@ -1,61 +0,0 @@ -from functools import total_ordering - - -@total_ordering -class Min(object): - """ - An object that is less than any other object (except itself). - - Inspired by https://pypi.python.org/pypi/Extremes - - Examples:: - - >>> import sys - - >>> Min < -sys.maxint - True - - >>> Min < None - True - - >>> Min < '' - True - - .. versionadded:: 0.2 - """ - def __lt__(self, other): - if other is Min: - return False - return True - - -@total_ordering -class Max(object): - """ - An object that is greater than any other object (except itself). - - Inspired by https://pypi.python.org/pypi/Extremes - - Examples:: - - >>> import sys - - >>> Max > Min - True - - >>> Max > sys.maxint - True - - >>> Max > 99999999999999999 - True - - .. versionadded:: 0.2 - """ - def __gt__(self, other): - if other is Max: - return False - return True - - -Min = Min() -Max = Max() diff --git a/validators/length.py b/validators/length.py index d0f91fd3..4c851333 100644 --- a/validators/length.py +++ b/validators/length.py @@ -1,37 +1,41 @@ +"""Length.""" +# -*- coding: utf-8 -*- + +# project from .between import between from .utils import validator @validator -def length(value, min=None, max=None): - """ - Return whether or not the length of given string is within a specified - range. +def length(value: str, /, *, min_val: int = 0, max_val: int = 0): + """Return whether or not the length of given string is within a specified range. Examples:: - >>> length('something', min=2) - True + >>> length('something', min_val=2) + # Output: True + + >>> length('something', min_val=9, max_val=9) + # Output: True - >>> length('something', min=9, max=9) - True + >>> length('something', max_val=5) + # Output: ValidationFailure(func=length, ...) - >>> length('something', max=5) - ValidationFailure(func=length, ...) + Args: + `value`: + [Required] The string to validate. + `min_val`: + [Optional] The minimum required length of the string. If not provided, + minimum length will not be checked. + `max_val`: + [Optional] The maximum length of the string. If not provided, + maximum length will not be checked. + Either one of `min_val` or `max_val` must be provided. - :param value: - The string to validate. - :param min: - The minimum required length of the string. If not provided, minimum - length will not be checked. - :param max: - The maximum length of the string. If not provided, maximum length - will not be checked. + Returns: + A `boolean` if `value` is greater than `min_val` and + less than `max_val`. .. versionadded:: 0.2 """ - if (min is not None and min < 0) or (max is not None and max < 0): - raise AssertionError( - '`min` and `max` need to be greater than zero.' - ) - return between(len(value), min=min, max=max) + return between(len(value), min_val=min_val, max_val=max_val)