diff --git a/docs/index.md b/docs/index.md index ae76ef2a..53d151d6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,7 +14,7 @@ ::: validators.iban - +::: validators.ip_address ::: validators.length diff --git a/poetry.lock b/poetry.lock index fa8d1b20..55104e53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1316,4 +1316,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "2503c8f28f5fefc9238c258ebef5cfb19013a2558f1b644e25b2c84bdc18a2f6" +content-hash = "3d0d330e676d623b065cd7111aa7bc28a4dd44c7bba6f97938d7e54009546193" diff --git a/tests/test_ip_address.py b/tests/test_ip_address.py new file mode 100644 index 00000000..98a977fa --- /dev/null +++ b/tests/test_ip_address.py @@ -0,0 +1,103 @@ +"""Test IP Address.""" +# -*- coding: utf-8 -*- + +# external +import pytest + +# local +from validators import ipv4, ipv6, ValidationFailure + + +@pytest.mark.parametrize( + ("address",), + [ + ("127.0.0.1",), + ("123.5.77.88",), + ("12.12.12.12",), + # w/ cidr + ("127.0.0.1/0",), + ("123.5.77.88/8",), + ("12.12.12.12/32",), + ], +) +def test_returns_true_on_valid_ipv4_address(address: str): + """Test returns true on valid ipv4 address.""" + assert ipv4(address) + assert not ipv6(address) + + +@pytest.mark.parametrize( + ("address",), + [ + # leading zeroes error-out from Python 3.9.5 + # ("100.100.033.033",), + ("900.200.100.75",), + ("0127.0.0.1",), + ("abc.0.0.1",), + # w/ cidr + ("1.1.1.1/-1",), + ("1.1.1.1/33",), + ("1.1.1.1/foo",), + ], +) +def test_returns_failed_validation_on_invalid_ipv4_address(address: str): + """Test returns failed validation on invalid ipv4 address.""" + assert isinstance(ipv4(address), ValidationFailure) + + +@pytest.mark.parametrize( + ("address",), + [ + ("::",), + ("::1",), + ("1::",), + ("dead:beef:0:0:0:0000:42:1",), + ("abcd:ef::42:1",), + ("0:0:0:0:0:ffff:1.2.3.4",), + ("::192.168.30.2",), + ("0000:0000:0000:0000:0000::",), + ("0:a:b:c:d:e:f::",), + # w/ cidr + ("::1/128",), + ("::1/0",), + ("dead:beef:0:0:0:0:42:1/8",), + ("abcd:ef::42:1/32",), + ("0:0:0:0:0:ffff:1.2.3.4/16",), + ("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64",), + ("::192.168.30.2/128",), + ], +) +def test_returns_true_on_valid_ipv6_address(address: str): + """Test returns true on valid ipv6 address.""" + assert ipv6(address) + assert not ipv4(address) + + +@pytest.mark.parametrize( + ("address",), + [ + ("abc.0.0.1",), + ("abcd:1234::123::1",), + ("1:2:3:4:5:6:7:8:9",), + ("1:2:3:4:5:6:7:8::",), + ("1:2:3:4:5:6:7::8:9",), + ("abcd::1ffff",), + ("1111:",), + (":8888",), + (":1.2.3.4",), + ("18:05",), + (":",), + (":1:2:",), + (":1:2::",), + ("::1:2::",), + ("8::1:2::9",), + ("02001:0000:1234:0000:0000:C1C0:ABCD:0876",), + # w/ cidr + ("::1/129",), + ("::1/-1",), + ("::1/foo",), + ], +) +def test_returns_failed_validation_on_invalid_ipv6_address(address: str): + """Test returns failed validation on invalid ipv6 address.""" + assert isinstance(ipv6(address), ValidationFailure) diff --git a/tests/test_ipv4.py b/tests/test_ipv4.py deleted file mode 100644 index f0f2f372..00000000 --- a/tests/test_ipv4.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest - -from validators import ipv4, ipv6, ValidationFailure - - -@pytest.mark.parametrize(('address',), [ - ('127.0.0.1',), - ('123.5.77.88',), - ('12.12.12.12',), -]) -def test_returns_true_on_valid_ipv4_address(address): - assert ipv4(address) - assert not ipv6(address) - - -@pytest.mark.parametrize(('address',), [ - ('abc.0.0.1',), - ('1278.0.0.1',), - ('127.0.0.abc',), - ('900.200.100.75',), - ('0127.0.0.1',), -]) -def test_returns_failed_validation_on_invalid_ipv4_address(address): - assert isinstance(ipv4(address), ValidationFailure) diff --git a/tests/test_ipv4_cidr.py b/tests/test_ipv4_cidr.py deleted file mode 100644 index 3216a17a..00000000 --- a/tests/test_ipv4_cidr.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest - -from validators import ipv4_cidr, ipv6_cidr, ValidationFailure - - -@pytest.mark.parametrize(('cidr',), [ - ('127.0.0.1/0',), - ('123.5.77.88/8',), - ('12.12.12.12/32',), -]) -def test_returns_true_on_valid_ipv4_cidr(cidr): - assert ipv4_cidr(cidr) - assert not ipv6_cidr(cidr) - - -@pytest.mark.parametrize(('cidr',), [ - ('abc.0.0.1',), - ('1.1.1.1',), - ('1.1.1.1/-1',), - ('1.1.1.1/33',), - ('1.1.1.1/foo',), -]) -def test_returns_failed_validation_on_invalid_ipv4_cidr(cidr): - assert isinstance(ipv4_cidr(cidr), ValidationFailure) diff --git a/tests/test_ipv6.py b/tests/test_ipv6.py deleted file mode 100644 index 286f1fb5..00000000 --- a/tests/test_ipv6.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest - -from validators import ipv4, ipv6, ValidationFailure - - -@pytest.mark.parametrize(('address',), [ - ('::',), - ('::1',), - ('1::',), - ('dead:beef:0:0:0:0000:42:1',), - ('abcd:ef::42:1',), - ('0:0:0:0:0:ffff:1.2.3.4',), - ('::192.168.30.2',), - ('0000:0000:0000:0000:0000::',), - ('0:a:b:c:d:e:f::',), -]) -def test_returns_true_on_valid_ipv6_address(address): - assert ipv6(address) - assert not ipv4(address) - - -@pytest.mark.parametrize(('address',), [ - ('abc.0.0.1',), - ('abcd:1234::123::1',), - ('1:2:3:4:5:6:7:8:9',), - ('1:2:3:4:5:6:7:8::',), - ('1:2:3:4:5:6:7::8:9',), - ('abcd::1ffff',), - ('1111:',), - (':8888',), - (':1.2.3.4',), - ('18:05',), - (':',), - (':1:2:',), - (':1:2::',), - ('::1:2::',), - ('8::1:2::9',), - ('02001:0000:1234:0000:0000:C1C0:ABCD:0876',), -]) -def test_returns_failed_validation_on_invalid_ipv6_address(address): - assert isinstance(ipv6(address), ValidationFailure) diff --git a/tests/test_ipv6_cidr.py b/tests/test_ipv6_cidr.py deleted file mode 100644 index 308390a9..00000000 --- a/tests/test_ipv6_cidr.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest - -from validators import ipv4_cidr, ipv6_cidr, ValidationFailure - - -@pytest.mark.parametrize(('cidr',), [ - ('::1/0',), - ('dead:beef:0:0:0:0:42:1/8',), - ('abcd:ef::42:1/32',), - ('0:0:0:0:0:ffff:1.2.3.4/64',), - ('::192.168.30.2/128',), -]) -def test_returns_true_on_valid_ipv6_cidr(cidr): - assert ipv6_cidr(cidr) - assert not ipv4_cidr(cidr) - - -@pytest.mark.parametrize(('cidr',), [ - ('abc.0.0.1',), - ('abcd:1234::123::1',), - ('1:2:3:4:5:6:7:8:9',), - ('abcd::1ffff',), - ('1.1.1.1',), - ('::1',), - ('::1/129',), - ('::1/-1',), - ('::1/foo',), -]) -def test_returns_failed_validation_on_invalid_ipv6_cidr(cidr): - assert isinstance(ipv6_cidr(cidr), ValidationFailure) diff --git a/validators/__init__.py b/validators/__init__.py index f837445f..2005ed88 100644 --- a/validators/__init__.py +++ b/validators/__init__.py @@ -9,7 +9,7 @@ from .hashes import md5, sha1, sha224, sha256, sha512 from .i18n import fi_business_id, fi_ssn from .iban import iban -from .ip_address import ipv4, ipv4_cidr, ipv6, ipv6_cidr +from .ip_address import ipv4, ipv6 from .length import length from .mac_address import mac_address from .slug import slug @@ -29,9 +29,7 @@ "fi_business_id", "fi_ssn", "iban", - "ipv4_cidr", "ipv4", - "ipv6_cidr", "ipv6", "jcb", "length", diff --git a/validators/ip_address.py b/validators/ip_address.py index e0c061db..309bd674 100644 --- a/validators/ip_address.py +++ b/validators/ip_address.py @@ -1,156 +1,111 @@ +"""IP Address.""" + +# standard +from ipaddress import ( + AddressValueError, + NetmaskValueError, + IPv4Address, + IPv4Network, + IPv6Address, + IPv6Network, +) + +# local from .utils import validator @validator -def ipv4(value): - """ - Return whether a given value is a valid IP version 4 address. - - This validator is based on `WTForms IPAddress validator`_ +def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False): + """Returns whether a given value is a valid IPv4 address. - .. _WTForms IPAddress validator: - https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py + From Python version 3.9.5 leading zeros are no longer tolerated + and are treated as an error. The initial version of ipv4 validator + was inspired from [WTForms IPAddress validator][1]. - Examples:: + [1]: https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py + Examples: >>> ipv4('123.0.0.7') - True - + # Output: True + >>> ipv4('1.1.1.1/8') + # Output: True >>> ipv4('900.80.70.11') - ValidationFailure(func=ipv4, args={'value': '900.80.70.11'}) - - .. versionadded:: 0.2 - - :param value: IP address string to validate - """ - groups = value.split(".") - if ( - len(groups) != 4 - or any(not x.isdigit() for x in groups) - or any(len(x) > 3 for x in groups) - ): - return False - return all(0 <= int(part) < 256 for part in groups) - - -@validator -def ipv4_cidr(value): - """ - Return whether a given value is a valid CIDR-notated IP version 4 - address range. - - This validator is based on RFC4632 3.1. - - Examples:: - - >>> ipv4_cidr('1.1.1.1/8') - True - - >>> ipv4_cidr('1.1.1.1') - ValidationFailure(func=ipv4_cidr, args={'value': '1.1.1.1'}) + # Output: ValidationFailure(func=ipv4, args={'value': '900.80.70.11'}) + + Args: + value: + IP address string to validate. + cidr: + IP address string may contain CIDR annotation + strict: + If strict is True and host bits are set in the supplied address. + Otherwise, the host bits are masked out to determine the + appropriate network address. ref [IPv4Network][2]. + [2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network + + Returns: + (Literal[True]): + If `value` is a valid IPv4 address. + (ValidationFailure): + If `value` is an invalid IPv4 address. + + Note: + - *In version 0.14.0*: + - Add supports for CIDR notation + + > *New in version 0.2.0* """ try: - prefix, suffix = value.split('/', 2) - except ValueError: + if cidr and value.count("/") == 1: + return IPv4Network(value, strict=strict) + return IPv4Address(value) + except (AddressValueError, NetmaskValueError): return False - if not ipv4(prefix) or not suffix.isdigit(): - return False - return 0 <= int(suffix) <= 32 @validator -def ipv6(value): - """ - Return whether a given value is a valid IP version 6 address - (including IPv4-mapped IPv6 addresses). +def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False): + """Returns if a given value is a valid IPv6 address. - This validator is based on `WTForms IPAddress validator`_. + Including IPv4-mapped IPv6 addresses. The initial version of ipv6 validator + was inspired from [WTForms IPAddress validator][1]. - .. _WTForms IPAddress validator: - https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py - - Examples:: - - >>> ipv6('abcd:ef::42:1') - True + [1]: https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py + Examples: >>> ipv6('::ffff:192.0.2.128') - True - - >>> ipv6('::192.0.2.128') - True - + # Output: True + >>> ipv6('::1/128') + # Output: True >>> ipv6('abc.0.0.1') - ValidationFailure(func=ipv6, args={'value': 'abc.0.0.1'}) - - .. versionadded:: 0.2 - - :param value: IP address string to validate - """ - ipv6_groups = value.split(':') - if len(ipv6_groups) == 1: - return False - ipv4_groups = ipv6_groups[-1].split('.') - - if len(ipv4_groups) > 1: - if not ipv4(ipv6_groups[-1]): - return False - ipv6_groups = ipv6_groups[:-1] - else: - ipv4_groups = [] - - count_blank = 0 - for part in ipv6_groups: - if not part: - count_blank += 1 - continue - try: - num = int(part, 16) - except ValueError: - return False - else: - if not 0 <= num <= 65536 or len(part) > 4: - return False - - max_groups = 6 if ipv4_groups else 8 - part_count = len(ipv6_groups) - count_blank - if count_blank == 0 and part_count == max_groups: - # no :: -> must have size of max_groups - return True - elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0] and part_count < max_groups: - # one :: inside the address or prefix or suffix : -> filter least two cases - return True - elif count_blank == 2 and part_count < max_groups and ( - ((ipv6_groups[0] and not ipv6_groups[-1]) or (not ipv6_groups[0] and ipv6_groups[-1])) or ipv4_groups): - # leading or trailing :: or : at end and begin -> filter last case - # Check if it has ipv4 groups because they get removed from the ipv6_groups - return True - elif count_blank == 3 and part_count == 0: - # :: is the address -> filter everything else - return True - return False - - -@validator -def ipv6_cidr(value): - """ - Returns whether a given value is a valid CIDR-notated IP version 6 - address range. - - This validator is based on RFC4632 3.1. - - Examples:: - - >>> ipv6_cidr('::1/128') - True - - >>> ipv6_cidr('::1') - ValidationFailure(func=ipv6_cidr, args={'value': '::1'}) + # Output: ValidationFailure(func=ipv6, args={'value': 'abc.0.0.1'}) + + Args: + value: + IP address string to validate. + cidr: + IP address string may contain CIDR annotation + strict: + If strict is True and host bits are set in the supplied address. + Otherwise, the host bits are masked out to determine the + appropriate network address. ref [IPv6Network][2]. + [2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv6Network + + Returns: + (Literal[True]): + If `value` is a valid IPv6 address. + (ValidationFailure): + If `value` is an invalid IPv6 address. + + Note: + - *In version 0.14.0*: + - Add supports for CIDR notation + + > *New in version 0.2.0* """ try: - prefix, suffix = value.split('/', 2) - except ValueError: - return False - if not ipv6(prefix) or not suffix.isdigit(): + if cidr and value.count("/") == 1: + return IPv6Network(value, strict=strict) + return IPv6Address(value) + except (AddressValueError, NetmaskValueError): return False - return 0 <= int(suffix) <= 128 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy