bpo-27860: ipaddress: fix Interface missed some attributes (GH-12836)
IPv4Interface and IPv6Interface did not has netmask and hostmask attributes when its argument is bytes or int. This commit extracts method for constructors of Network and Interface, and ensure Interface class always provides them.
This commit is contained in:
parent
74125a60b7
commit
6fa84bd12c
110
Lib/ipaddress.py
110
Lib/ipaddress.py
|
@ -532,6 +532,30 @@ class _IPAddressBase:
|
|||
except ValueError:
|
||||
cls._report_invalid_netmask(ip_str)
|
||||
|
||||
@classmethod
|
||||
def _split_addr_prefix(cls, address):
|
||||
"""Helper function to parse address of Network/Interface.
|
||||
|
||||
Arg:
|
||||
address: Argument of Network/Interface.
|
||||
|
||||
Returns:
|
||||
(addr, prefix) tuple.
|
||||
"""
|
||||
# a packed address or integer
|
||||
if isinstance(address, (bytes, int)):
|
||||
return address, cls._max_prefixlen
|
||||
|
||||
if not isinstance(address, tuple):
|
||||
# Assume input argument to be string or any object representation
|
||||
# which converts into a formatted IP prefix string.
|
||||
address = _split_optional_netmask(address)
|
||||
|
||||
# Constructing from a tuple (addr, [mask])
|
||||
if len(address) > 1:
|
||||
return address
|
||||
return address[0], cls._max_prefixlen
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (str(self),)
|
||||
|
||||
|
@ -1305,32 +1329,16 @@ class IPv4Address(_BaseV4, _BaseAddress):
|
|||
class IPv4Interface(IPv4Address):
|
||||
|
||||
def __init__(self, address):
|
||||
if isinstance(address, (bytes, int)):
|
||||
IPv4Address.__init__(self, address)
|
||||
self.network = IPv4Network(self._ip)
|
||||
self._prefixlen = self._max_prefixlen
|
||||
return
|
||||
addr, mask = self._split_addr_prefix(address)
|
||||
|
||||
if isinstance(address, tuple):
|
||||
IPv4Address.__init__(self, address[0])
|
||||
if len(address) > 1:
|
||||
self._prefixlen = int(address[1])
|
||||
else:
|
||||
self._prefixlen = self._max_prefixlen
|
||||
|
||||
self.network = IPv4Network(address, strict=False)
|
||||
self.netmask = self.network.netmask
|
||||
self.hostmask = self.network.hostmask
|
||||
return
|
||||
|
||||
addr = _split_optional_netmask(address)
|
||||
IPv4Address.__init__(self, addr[0])
|
||||
|
||||
self.network = IPv4Network(address, strict=False)
|
||||
IPv4Address.__init__(self, addr)
|
||||
self.network = IPv4Network((addr, mask), strict=False)
|
||||
self.netmask = self.network.netmask
|
||||
self._prefixlen = self.network._prefixlen
|
||||
|
||||
self.netmask = self.network.netmask
|
||||
self.hostmask = self.network.hostmask
|
||||
@functools.cached_property
|
||||
def hostmask(self):
|
||||
return self.network.hostmask
|
||||
|
||||
def __str__(self):
|
||||
return '%s/%d' % (self._string_from_ip_int(self._ip),
|
||||
|
@ -1435,20 +1443,7 @@ class IPv4Network(_BaseV4, _BaseNetwork):
|
|||
ValueError: If strict is True and a network address is not
|
||||
supplied.
|
||||
"""
|
||||
# Constructing from a packed address or integer
|
||||
if isinstance(address, (int, bytes)):
|
||||
addr = address
|
||||
mask = self._max_prefixlen
|
||||
# Constructing from a tuple (addr, [mask])
|
||||
elif isinstance(address, tuple):
|
||||
addr = address[0]
|
||||
mask = address[1] if len(address) > 1 else self._max_prefixlen
|
||||
# Assume input argument to be string or any object representation
|
||||
# which converts into a formatted IP prefix string.
|
||||
else:
|
||||
args = _split_optional_netmask(address)
|
||||
addr = self._ip_int_from_string(args[0])
|
||||
mask = args[1] if len(args) == 2 else self._max_prefixlen
|
||||
addr, mask = self._split_addr_prefix(address)
|
||||
|
||||
self.network_address = IPv4Address(addr)
|
||||
self.netmask, self._prefixlen = self._make_netmask(mask)
|
||||
|
@ -1979,28 +1974,16 @@ class IPv6Address(_BaseV6, _BaseAddress):
|
|||
class IPv6Interface(IPv6Address):
|
||||
|
||||
def __init__(self, address):
|
||||
if isinstance(address, (bytes, int)):
|
||||
IPv6Address.__init__(self, address)
|
||||
self.network = IPv6Network(self._ip)
|
||||
self._prefixlen = self._max_prefixlen
|
||||
return
|
||||
if isinstance(address, tuple):
|
||||
IPv6Address.__init__(self, address[0])
|
||||
if len(address) > 1:
|
||||
self._prefixlen = int(address[1])
|
||||
else:
|
||||
self._prefixlen = self._max_prefixlen
|
||||
self.network = IPv6Network(address, strict=False)
|
||||
self.netmask = self.network.netmask
|
||||
self.hostmask = self.network.hostmask
|
||||
return
|
||||
addr, mask = self._split_addr_prefix(address)
|
||||
|
||||
addr = _split_optional_netmask(address)
|
||||
IPv6Address.__init__(self, addr[0])
|
||||
self.network = IPv6Network(address, strict=False)
|
||||
IPv6Address.__init__(self, addr)
|
||||
self.network = IPv6Network((addr, mask), strict=False)
|
||||
self.netmask = self.network.netmask
|
||||
self._prefixlen = self.network._prefixlen
|
||||
self.hostmask = self.network.hostmask
|
||||
|
||||
@functools.cached_property
|
||||
def hostmask(self):
|
||||
return self.network.hostmask
|
||||
|
||||
def __str__(self):
|
||||
return '%s/%d' % (self._string_from_ip_int(self._ip),
|
||||
|
@ -2110,20 +2093,7 @@ class IPv6Network(_BaseV6, _BaseNetwork):
|
|||
ValueError: If strict was True and a network address was not
|
||||
supplied.
|
||||
"""
|
||||
# Constructing from a packed address or integer
|
||||
if isinstance(address, (int, bytes)):
|
||||
addr = address
|
||||
mask = self._max_prefixlen
|
||||
# Constructing from a tuple (addr, [mask])
|
||||
elif isinstance(address, tuple):
|
||||
addr = address[0]
|
||||
mask = address[1] if len(address) > 1 else self._max_prefixlen
|
||||
# Assume input argument to be string or any object representation
|
||||
# which converts into a formatted IP prefix string.
|
||||
else:
|
||||
args = _split_optional_netmask(address)
|
||||
addr = self._ip_int_from_string(args[0])
|
||||
mask = args[1] if len(args) == 2 else self._max_prefixlen
|
||||
addr, mask = self._split_addr_prefix(address)
|
||||
|
||||
self.network_address = IPv6Address(addr)
|
||||
self.netmask, self._prefixlen = self._make_netmask(mask)
|
||||
|
|
|
@ -399,7 +399,13 @@ class NetmaskTestMixin_v4(CommonTestMixin_v4):
|
|||
"""Input validation on interfaces and networks is very similar"""
|
||||
|
||||
def test_no_mask(self):
|
||||
self.assertEqual(str(self.factory('1.2.3.4')), '1.2.3.4/32')
|
||||
for address in ('1.2.3.4', 0x01020304, b'\x01\x02\x03\x04'):
|
||||
net = self.factory(address)
|
||||
self.assertEqual(str(net), '1.2.3.4/32')
|
||||
self.assertEqual(str(net.netmask), '255.255.255.255')
|
||||
self.assertEqual(str(net.hostmask), '0.0.0.0')
|
||||
# IPv4Network has prefixlen, but IPv4Interface doesn't.
|
||||
# Should we add it to IPv4Interface too? (bpo-36392)
|
||||
|
||||
def test_split_netmask(self):
|
||||
addr = "1.2.3.4/32/24"
|
||||
|
@ -527,6 +533,15 @@ class NetworkTestCase_v4(BaseTestCase, NetmaskTestMixin_v4):
|
|||
class NetmaskTestMixin_v6(CommonTestMixin_v6):
|
||||
"""Input validation on interfaces and networks is very similar"""
|
||||
|
||||
def test_no_mask(self):
|
||||
for address in ('::1', 1, b'\x00'*15 + b'\x01'):
|
||||
net = self.factory(address)
|
||||
self.assertEqual(str(net), '::1/128')
|
||||
self.assertEqual(str(net.netmask), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')
|
||||
self.assertEqual(str(net.hostmask), '::')
|
||||
# IPv6Network has prefixlen, but IPv6Interface doesn't.
|
||||
# Should we add it to IPv4Interface too? (bpo-36392)
|
||||
|
||||
def test_split_netmask(self):
|
||||
addr = "cafe:cafe::/128/190"
|
||||
with self.assertAddressError("Only one '/' permitted in %r" % addr):
|
||||
|
|
Loading…
Reference in New Issue