diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 7a3f36f3bc0..909a55de4f1 100644 --- a/Lib/ipaddress.py +++ b/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) diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 15317c94463..20316f15f8c 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -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):