diff --git a/CHANGES.md b/CHANGES.md index 5309e0e3..ab7f9122 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,19 +1,50 @@ # Changelog + + +## 0.22.0 (2023-09-02) + +_**What's Changed**_ + +> - _Breaking_: +> - API changes in `validators.ipv4` and `validators.ipv6` functions +> - `strict` parameter now correctly validates IP addresses strictly in CIDR notation +> - `host_bit` parameter distinguishes between network and host IP address + +- fix: url validator considers urls with /#/ as valid by @adrienthiery in [#289](https://github.com/python-validators/validators/pull/289) +- Add note about ValidationFailure to ValidationError in changes.md by @tswfi in [#291](https://github.com/python-validators/validators/pull/291) +- fix: simple hostname validation regex by @joe733 in [#294](https://github.com/python-validators/validators/pull/294) +- fix: strict CIDR IP validation; bump version by @joe733 in [#295](https://github.com/python-validators/validators/pull/295) + +_**New Contributors**_ + +- @adrienthiery made their first contribution in [#289](https://github.com/python-validators/validators/pull/289) +- @tswfi made their first contribution in [#291](https://github.com/python-validators/validators/pull/291) + +**Full Changelog**: [`0.21.2...0.22.0`](https://github.com/python-validators/validators/compare/0.21.2...0.22.0) + ## 0.21.2 (2023-08-07) -### What's Changed +_**What's Changed**_ + +> - _Breaking_: +> - `ValidationFailure` renamed to `ValidationError` in [`joe733@12ae1f5`](https://github.com/joe733/pyvalidators/commit/12ae1f5850555d11e1f1a2c03f597fd10610215a) -- feat: refactoring; updates; fixes; bump version by @joe733 in [#283](https://github.com/python-validators/validators/pull/283) -- *Breaking Changes*: - - `ValidationFailure` renamed to `ValidationError` in [joe733@12ae1f5](https://github.com/joe733/pyvalidators/commit/12ae1f5850555d11e1f1a2c03f597fd10610215a) +- feat: refactoring; updates; fixes; bump version by @joe733 in [#283](https://github.com/python-validators/validators/pull/283)() - build(deps): bump pymdown-extensions from 9.11 to 10.0 by @dependabot in [#273](https://github.com/python-validators/validators/pull/273) - build(deps): bump requests from 2.28.2 to 2.31.0 by @dependabot in [#275](https://github.com/python-validators/validators/pull/275) - add validator ETH addresses (ERC20) by @msamsami in [#276](https://github.com/python-validators/validators/pull/276) - Added Country Code Validation by @aviiciii in [#280](https://github.com/python-validators/validators/pull/280) - build(deps-dev): bump certifi from 2022.12.7 to 2023.7.22 by @dependabot in [#281](https://github.com/python-validators/validators/pull/281) -### New Contributors +_**New Contributors**_ - @dependabot made their first contribution in [#273](https://github.com/python-validators/validators/pull/273) - @msamsami made their first contribution in [#276](https://github.com/python-validators/validators/pull/276) @@ -33,6 +64,9 @@ ## 0.21.0 (2023-03-25) +> - _Breaking_: +> - Couple of API changes, refer [documentation](https://python-validators.github.io/validators/) + - feat: add build for pypi workflow by @joe733 in [#255](https://github.com/python-validators/validators/pull/255) - feat: @validator now catches `Exception` by @joe733 in [#254](https://github.com/python-validators/validators/pull/254) - maint: improves `i18n` package by @joe733 in [#252](https://github.com/python-validators/validators/pull/252) @@ -125,11 +159,11 @@ ## 0.14.0 (2019-08-21) -- Added new validators ``ipv4_cidr``, ``ipv6_cidr`` (#117, pull request courtesy woodruffw) +- Added new validators `ipv4_cidr`, `ipv6_cidr` (#117, pull request courtesy woodruffw) ## 0.13.0 (2019-05-20) -- Added new validator: ``es_doi``, ``es_nif``, ``es_cif``, ``es_nie`` (#121, pull request courtesy kingbuzzman) +- Added new validator: `es_doi`, `es_nif`, `es_cif`, `es_nie` (#121, pull request courtesy kingbuzzman) ## 0.12.6 (2019-05-08) @@ -184,7 +218,7 @@ ## 0.10.3 (2016-06-13) -- Added ``public`` parameter to url validator (#26, pull request courtesy Iconceicao) +- Added `public` parameter to url validator (#26, pull request courtesy Iconceicao) ## 0.10.2 (2016-06-11) @@ -197,48 +231,48 @@ ## 0.10.0 (2016-01-09) -- Added support for internationalized domain names in ``domain`` validator +- Added support for internationalized domain names in `domain` validator ## 0.9.0 (2015-10-10) -- Added new validator: ``domain`` +- Added new validator: `domain` - Added flake8 and isort checks in travis config ## 0.8.0 (2015-06-24) -- Added new validator: ``iban`` +- Added new validator: `iban` ## 0.7.0 (2014-09-07) - Fixed errors in code examples. -- Fixed ``TypeError`` when using ``between`` validator with ``datetime`` objects +- Fixed `TypeError` when using `between` validator with `datetime` objects like in the code example. -- Changed validators to always return ``True`` instead of a truthy object when +- Changed validators to always return `True` instead of a truthy object when the validation succeeds. -- Fixed ``truthy`` validator to work like it's name suggests. Previously it - worked like ``falsy``. +- Fixed `truthy` validator to work like it's name suggests. Previously it + worked like `falsy`. ## 0.6.0 (2014-06-25) -- Added new validator: ``slug`` +- Added new validator: `slug` ## 0.5.0 (2013-10-31) -- Renamed ``finnish_business_id`` to ``fi_business_id`` -- Added new validator: ``fi_ssn`` +- Renamed `finnish_business_id` to `fi_business_id` +- Added new validator: `fi_ssn` ## 0.4.0 (2013-10-29) -- Added new validator: ``finnish_business_id`` +- Added new validator: `finnish_business_id` ## 0.3.0 (2013-10-27) -- ``number_range`` -> ``between`` +- `number_range` -> `between` ## 0.2.0 (2013-10-22) -- Various new validators: ``ipv4``, ``ipv6``, ``length``, ``number_range``, - ``mac_address``, ``url``, ``uuid`` +- Various new validators: `ipv4`, `ipv6`, `length`, `number_range`, + `mac_address`, `url`, `uuid` ## 0.1.0 (2013-10-18) diff --git a/SECURITY.md b/SECURITY.md index 93547882..901b7681 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,7 @@ | Version | Supported | | ---------- | ------------------ | -| `>=0.21.2` | :white_check_mark: | +| `>=0.22.0` | :white_check_mark: | ## Reporting a Vulnerability diff --git a/pyproject.toml b/pyproject.toml index 64e521ee..4c1d39b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta" [project] name = "validators" -version = "0.21.2" +version = "0.22.0" description = "Python Data Validation for Humans™" authors = [{ name = "Konsta Vesterinen", email = "konsta@fastmonkeys.com" }] license = { text = "MIT" } diff --git a/src/validators/__init__.py b/src/validators/__init__.py index 2809a585..a7ca68eb 100644 --- a/src/validators/__init__.py +++ b/src/validators/__init__.py @@ -77,4 +77,4 @@ "validator", ) -__version__ = "0.21.2" +__version__ = "0.22.0" diff --git a/src/validators/ip_address.py b/src/validators/ip_address.py index 3ae638d7..e6d01f65 100644 --- a/src/validators/ip_address.py +++ b/src/validators/ip_address.py @@ -15,7 +15,7 @@ @validator -def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False): +def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bool = True): """Returns whether a given value is a valid IPv4 address. From Python version 3.9.5 leading zeros are no longer tolerated @@ -36,11 +36,12 @@ def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False): value: IP address string to validate. cidr: - IP address string may contain CIDR annotation + IP address string may contain CIDR notation 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]. + IP address string is strictly in CIDR notation + host_bit: + If `False` and host bits (along with network bits) _are_ set in the supplied + address, this function raises a validation error. ref [IPv4Network][2]. [2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network Returns: @@ -58,15 +59,17 @@ def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False): if not value: return False try: - if cidr and value.count("/") == 1: - return IPv4Network(value, strict=strict) + if cidr: + if strict and value.count("/") != 1: + raise ValueError("IPv4 address was expected in CIDR notation") + return IPv4Network(value, strict=not host_bit) return IPv4Address(value) - except (AddressValueError, NetmaskValueError): + except (ValueError, AddressValueError, NetmaskValueError): return False @validator -def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False): +def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bool = True): """Returns if a given value is a valid IPv6 address. Including IPv4-mapped IPv6 addresses. The initial version of ipv6 validator @@ -88,9 +91,10 @@ def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False): 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]. + IP address string is strictly in CIDR notation + host_bit: + If `False` and host bits (along with network bits) _are_ set in the supplied + address, this function raises a validation error. ref [IPv6Network][2]. [2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv6Network Returns: @@ -108,8 +112,10 @@ def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False): if not value: return False try: - if cidr and value.count("/") == 1: - return IPv6Network(value, strict=strict) + if cidr: + if strict and value.count("/") != 1: + raise ValueError("IPv6 address was expected in CIDR notation") + return IPv6Network(value, strict=not host_bit) return IPv6Address(value) - except (AddressValueError, NetmaskValueError): + except (ValueError, AddressValueError, NetmaskValueError): return False diff --git a/tests/test_ip_address.py b/tests/test_ip_address.py index 312e98ef..f28cdec0 100644 --- a/tests/test_ip_address.py +++ b/tests/test_ip_address.py @@ -13,10 +13,6 @@ ("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): @@ -25,6 +21,22 @@ def test_returns_true_on_valid_ipv4_address(address: str): assert not ipv6(address) +@pytest.mark.parametrize( + ("address", "cidr", "strict", "host_bit"), + [ + ("127.0.0.1/0", True, True, True), + ("123.5.77.88", True, False, True), + ("12.12.12.0/24", True, True, False), + ], +) +def test_returns_true_on_valid_ipv4_cidr_address( + address: str, cidr: bool, strict: bool, host_bit: bool +): + """Test returns true on valid ipv4 CIDR address.""" + assert ipv4(address, cidr=cidr, strict=strict, host_bit=host_bit) + assert not ipv6(address, cidr=cidr, strict=strict, host_bit=host_bit) + + @pytest.mark.parametrize( ("address",), [ @@ -33,10 +45,6 @@ def test_returns_true_on_valid_ipv4_address(address: str): ("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): @@ -44,6 +52,22 @@ def test_returns_failed_validation_on_invalid_ipv4_address(address: str): assert isinstance(ipv4(address), ValidationError) +@pytest.mark.parametrize( + ("address", "cidr", "strict", "host_bit"), + [ + ("1.1.1.1/1", False, True, True), + ("1.1.1.1/33", True, False, True), + ("1.1.1.1/24", True, True, False), + ("1.1.1.1/-1", True, True, True), + ], +) +def test_returns_failed_validation_on_invalid_ipv4_cidr_address( + address: str, cidr: bool, strict: bool, host_bit: bool +): + """Test returns failed validation on invalid ipv4 CIDR address.""" + assert isinstance(ipv4(address, cidr=cidr, strict=strict, host_bit=host_bit), ValidationError) + + @pytest.mark.parametrize( ("address",), [ @@ -56,14 +80,6 @@ def test_returns_failed_validation_on_invalid_ipv4_address(address: str): ("::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): @@ -72,6 +88,26 @@ def test_returns_true_on_valid_ipv6_address(address: str): assert not ipv4(address) +@pytest.mark.parametrize( + ("address", "cidr", "strict", "host_bit"), + [ + ("::1/128", True, True, True), + ("::1/0", True, True, True), + ("dead:beef:0:0:0:0:42:1/8", True, True, True), + ("abcd:ef::42:1/32", True, True, True), + ("0:0:0:0:0:ffff:1.2.3.4/16", True, True, True), + ("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64", True, True, True), + ("::192.168.30.2/128", True, True, True), + ], +) +def test_returns_true_on_valid_ipv6_cidr_address( + address: str, cidr: bool, strict: bool, host_bit: bool +): + """Test returns true on valid ipv6 CIDR address.""" + assert ipv6(address, cidr=cidr, strict=strict, host_bit=host_bit) + assert not ipv4(address, cidr=cidr, strict=strict, host_bit=host_bit) + + @pytest.mark.parametrize( ("address",), [ @@ -91,12 +127,24 @@ def test_returns_true_on_valid_ipv6_address(address: str): ("::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), ValidationError) + + +@pytest.mark.parametrize( + ("address", "cidr", "strict", "host_bit"), + [ + ("::1/128", False, True, True), + ("::1/129", True, False, True), + ("dead:beef:0:0:0:0:42:1/8", True, True, False), + ("::1/-130", True, True, True), + ], +) +def test_returns_failed_validation_on_invalid_ipv6_cidr_address( + address: str, cidr: bool, strict: bool, host_bit: bool +): + """Test returns failed validation on invalid ipv6 CIDR address.""" + assert isinstance(ipv6(address, cidr=cidr, strict=strict, host_bit=host_bit), ValidationError)