Skip to content

Commit

Permalink
feat Add IsIP (#43)
Browse files Browse the repository at this point in the history
* feat Add IsIP

* Fix to tidy up some logic in IsIp
  • Loading branch information
osintalex authored Aug 25, 2022
1 parent c1eb138 commit 928a732
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 4 deletions.
3 changes: 2 additions & 1 deletion dirty_equals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
IsPositiveFloat,
IsPositiveInt,
)
from ._other import FunctionCheck, IsJson, IsUUID
from ._other import FunctionCheck, IsIP, IsJson, IsUUID
from ._sequence import Contains, HasLen, IsList, IsListOrTuple, IsTuple
from ._strings import IsAnyStr, IsBytes, IsStr

Expand Down Expand Up @@ -69,6 +69,7 @@
'FunctionCheck',
'IsJson',
'IsUUID',
'IsIP',
# strings
'IsStr',
'IsBytes',
Expand Down
62 changes: 60 additions & 2 deletions dirty_equals/_other.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import json
from typing import Any, Callable, TypeVar, overload
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_network
from typing import Any, Callable, Optional, TypeVar, Union, overload
from uuid import UUID

from ._base import DirtyEquals
from ._utils import plain_repr
from ._utils import Omit, plain_repr

try:
from typing import Literal
Expand Down Expand Up @@ -145,3 +146,60 @@ def is_even(x):

def equals(self, other: Any) -> bool:
return self.func(other)


IP = TypeVar('IP', IPv4Address, IPv4Network, IPv6Address, IPv6Network, Union[str, int, bytes])


class IsIP(DirtyEquals[IP]):
"""
A class that checks if a value is a valid IP address, optionally checking IP version, netmask.
"""

def __init__(self, *, version: Literal[None, 4, 6] = None, netmask: Optional[str] = None):
"""
Args:
version: The version of the IP to check, if omitted, versions 4 and 6 are both accepted.
netmask: The netmask of the IP to check, if omitted, any netmask is accepted. Requires version.
```py title="IsIP"
from ipaddress import IPv4Address, IPv6Address, IPv4Network
from dirty_equals import IsIP
assert '179.27.154.96' == IsIP
assert '179.27.154.96' == IsIP(version=4)
assert '2001:0db8:0a0b:12f0:0000:0000:0000:0001' == IsIP(version=6)
assert IPv4Address('127.0.0.1') == IsIP
assert IPv4Network('43.48.0.0/12') == IsIP
assert IPv6Address('::eeff:ae3f:d473') == IsIP
assert '54.43.53.219/10' == IsIP(version=4, netmask='255.192.0.0')
assert '54.43.53.219/10' == IsIP(version=4, netmask=4290772992)
assert '::ffff:aebf:d473/12' == IsIP(version=6, netmask='fff0::')
assert 3232235521 == IsIP
```
"""
self.version = version
if netmask and not self.version:
raise TypeError('To check the netmask you must specify the IP version')
self.netmask = netmask
super().__init__(version=version or Omit, netmask=netmask or Omit)

def equals(self, other: Any) -> bool:

if isinstance(other, (IPv4Network, IPv6Network)):
ip = other
elif isinstance(other, (str, bytes, int, IPv4Address, IPv6Address)):
ip = ip_network(other, strict=False)
else:
return False

if self.version:
if self.netmask:
version_check = self.version == ip.version
address_format = {4: IPv4Address, 6: IPv6Address}[self.version]
netmask_check = int(address_format(self.netmask)) == int(ip.netmask)
return version_check and netmask_check
elif self.version != ip.version:
return False

return True
2 changes: 2 additions & 0 deletions docs/types/other.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@
::: dirty_equals.AnyThing

::: dirty_equals.IsOneOf

::: dirty_equals.IsIP
57 changes: 56 additions & 1 deletion tests/test_other.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import uuid
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network

import pytest

from dirty_equals import FunctionCheck, IsJson, IsUUID
from dirty_equals import FunctionCheck, IsIP, IsJson, IsUUID


@pytest.mark.parametrize(
Expand Down Expand Up @@ -128,3 +129,57 @@ def foobar(v):
def test_json_both():
with pytest.raises(TypeError, match='IsJson requires either an argument or kwargs, not both'):
IsJson(1, a=2)


@pytest.mark.parametrize(
'other,dirty',
[
(IPv4Address('127.0.0.1'), IsIP()),
(IPv4Network('43.48.0.0/12'), IsIP()),
(IPv6Address('::eeff:ae3f:d473'), IsIP()),
(IPv6Network('::eeff:ae3f:d473/128'), IsIP()),
('2001:0db8:0a0b:12f0:0000:0000:0000:0001', IsIP()),
('179.27.154.96', IsIP),
('43.62.123.119', IsIP(version=4)),
('::ffff:2b3e:7b77', IsIP(version=6)),
('0:0:0:0:0:ffff:2b3e:7b77', IsIP(version=6)),
('54.43.53.219/10', IsIP(version=4, netmask='255.192.0.0')),
('::ffff:aebf:d473/12', IsIP(version=6, netmask='fff0::')),
('2001:0db8:0a0b:12f0:0000:0000:0000:0001', IsIP(version=6)),
(3232235521, IsIP()),
(b'\xC0\xA8\x00\x01', IsIP()),
(338288524927261089654018896845572831328, IsIP(version=6)),
(b'\x20\x01\x06\x58\x02\x2a\xca\xfe\x02\x00\x00\x00\x00\x00\x00\x01', IsIP(version=6)),
],
)
def test_is_ip_true(other, dirty):
assert other == dirty


@pytest.mark.parametrize(
'other,dirty',
[
('foobar', IsIP()),
([1, 2, 3], IsIP()),
('210.115.28.193', IsIP(version=6)),
('::ffff:d273:1cc1', IsIP(version=4)),
('210.115.28.193/12', IsIP(version=6, netmask='255.255.255.0')),
('::ffff:d273:1cc1', IsIP(version=6, netmask='fff0::')),
(3232235521, IsIP(version=6)),
(338288524927261089654018896845572831328, IsIP(version=4)),
],
)
def test_is_ip_false(other, dirty):
assert other != dirty


def test_not_ip_repr():
is_ip = IsIP()
with pytest.raises(AssertionError):
assert '123' == is_ip
assert str(is_ip) == 'IsIP()'


def test_ip_bad_netmask():
with pytest.raises(TypeError, match='To check the netmask you must specify the IP version'):
IsIP(netmask='255.255.255.0')

0 comments on commit 928a732

Please sign in to comment.