diff -r 64ed56fbc5e7 Doc/library/ipaddress.rst --- a/Doc/library/ipaddress.rst Sat Jun 25 03:06:58 2016 +0000 +++ b/Doc/library/ipaddress.rst Sat Jun 25 16:35:02 2016 +0200 @@ -544,6 +544,28 @@ >>> ip_network('192.0.2.0/24').supernet(new_prefix=20) IPv4Network('192.0.0.0/20') + .. method:: subnet_of(other) + + Returns *True* if this network is a subnet of *other*. + + >>> a = ip_network('192.168.1.0/24') + >>> b = ip_network('192.168.1.128/30') + >>> b.subnet_of(a) + True + + .. versionadded:: 3.6 + + .. method:: supernet_of(other) + + Returns *True* if this network is a supernet of *other*. + + >>> a = ip_network('192.168.1.0/24') + >>> b = ip_network('192.168.1.128/30') + >>> a.supernet_of(b) + True + + .. versionadded:: 3.6 + .. method:: compare_networks(other) Compare this network to *other*. In this comparison only the network @@ -619,6 +641,8 @@ .. method:: address_exclude(network) .. method:: subnets(prefixlen_diff=1, new_prefix=None) .. method:: supernet(prefixlen_diff=1, new_prefix=None) + .. method:: subnet_of(other) + .. method:: supernet_of(other) .. method:: compare_networks(other) Refer to the corresponding attribute documentation in diff -r 64ed56fbc5e7 Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst Sat Jun 25 03:06:58 2016 +0000 +++ b/Doc/whatsnew/3.5.rst Sat Jun 25 16:35:02 2016 +0200 @@ -132,7 +132,6 @@ :ref:`enhanced ` for improved performance and developer convenience. - Security improvements: * SSLv3 is now disabled throughout the standard library. diff -r 64ed56fbc5e7 Doc/whatsnew/3.6.rst --- a/Doc/whatsnew/3.6.rst Sat Jun 25 03:06:58 2016 +0000 +++ b/Doc/whatsnew/3.6.rst Sat Jun 25 16:35:02 2016 +0200 @@ -281,6 +281,16 @@ :issue:`23848`.) +ipaddress +--------- + +Instances of :class:`ipaddress.IPv6Network` and :class:`ipaddress.IPv6Network` +have two new functions: :meth:`~ipaddress._BaseNetwork.subnet_of` and +:meth:`~ipaddress._BaseNetwork.supernet_of`. +(Contributed by M. Albert in :issue:`20825`.) + + + idlelib and IDLE ---------------- diff -r 64ed56fbc5e7 Lib/ipaddress.py --- a/Lib/ipaddress.py Sat Jun 25 03:06:58 2016 +0000 +++ b/Lib/ipaddress.py Sat Jun 25 16:35:02 2016 +0200 @@ -776,8 +776,7 @@ if not isinstance(other, _BaseNetwork): raise TypeError("%s is not a network object" % other) - if not (other.network_address >= self.network_address and - other.broadcast_address <= self.broadcast_address): + if not other.subnet_of(self): raise ValueError('%s not contained in %s' % (other, self)) if other == self: return @@ -788,12 +787,10 @@ s1, s2 = self.subnets() while s1 != other and s2 != other: - if (other.network_address >= s1.network_address and - other.broadcast_address <= s1.broadcast_address): + if other.subnet_of(s1): yield s2 s1, s2 = s1.subnets() - elif (other.network_address >= s2.network_address and - other.broadcast_address <= s2.broadcast_address): + elif other.subnet_of(s2): yield s1 s1, s2 = s2.subnets() else: @@ -975,6 +972,34 @@ return (self.network_address.is_multicast and self.broadcast_address.is_multicast) + def _containment_check(self, check, other): + # always false if one is v4 and the other is v6. + if self._version != other._version: + raise TypeError("%s and %s are not of the same version" % ( + self, other)) + # dealing with another network. + if (hasattr(other, 'network_address') and + hasattr(other, 'broadcast_address')): + if check == 'subnet': + return (other.network_address <= self.network_address and + other.broadcast_address >= self.broadcast_address) + elif check == 'supernet': + return (other.network_address >= self.network_address and + other.broadcast_address <= self.broadcast_address) + else: + raise ValueError('%r is an unsupported check. Must be either ' + '"supernet" or "subnet"' % check) + # dealing with another address + else: + raise TypeError('Unable to test subnet containment with element ' + 'of type %s' % type(other)) + + def subnet_of(self, other): + return self._containment_check('subnet', other) + + def supernet_of(self, other): + return self._containment_check('supernet', other) + @property def is_reserved(self): """Test if the address is otherwise IETF reserved. diff -r 64ed56fbc5e7 Lib/test/test_ipaddress.py --- a/Lib/test/test_ipaddress.py Sat Jun 25 03:06:58 2016 +0000 +++ b/Lib/test/test_ipaddress.py Sat Jun 25 16:35:02 2016 +0200 @@ -92,7 +92,6 @@ y = pickle.loads(pickle.dumps(x, proto)) self.assertEqual(y, x) - class CommonTestMixin_v4(CommonTestMixin): def test_leading_zeros(self): @@ -477,6 +476,56 @@ class NetworkTestCase_v4(BaseTestCase, NetmaskTestMixin_v4): factory = ipaddress.IPv4Network + def test_subnet_of(self): + # containee left of container + self.assertFalse( + self.factory('10.0.0.0/30').subnet_of( + self.factory('10.0.1.0/24'))) + # containee inside container + self.assertTrue( + self.factory('10.0.0.0/30').subnet_of( + self.factory('10.0.0.0/24'))) + # containee right of container + self.assertFalse( + self.factory('10.0.0.0/30').subnet_of( + self.factory('10.0.1.0/24'))) + # containee larger than container + self.assertFalse( + self.factory('10.0.1.0/24').subnet_of( + self.factory('10.0.0.0/30'))) + + def test_supernet_of(self): + # containee left of container + self.assertFalse( + self.factory('10.0.0.0/30').supernet_of( + self.factory('10.0.1.0/24'))) + # containee inside container + self.assertFalse( + self.factory('10.0.0.0/30').supernet_of( + self.factory('10.0.0.0/24'))) + # containee right of container + self.assertFalse( + self.factory('10.0.0.0/30').supernet_of( + self.factory('10.0.1.0/24'))) + # containee larger than container + self.assertTrue( + self.factory('10.0.0.0/24').supernet_of( + self.factory('10.0.0.0/30'))) + + def test_mixed_types(self): + with self.assertRaises(TypeError): + ipaddress.IPv4Network('10.0.0.0/30').supernet_of( + ipaddress.IPv6Network('::1/128')) + with self.assertRaises(TypeError): + ipaddress.IPv6Network('::1/128').supernet_of( + ipaddress.IPv4Network('10.0.0.0/30')) + with self.assertRaises(TypeError): + ipaddress.IPv4Network('10.0.0.0/30').subnet_of( + ipaddress.IPv6Network('::1/128')) + with self.assertRaises(TypeError): + ipaddress.IPv6Network('::1/128').subnet_of( + ipaddress.IPv4Network('10.0.0.0/30')) + class NetmaskTestMixin_v6(CommonTestMixin_v6): """Input validation on interfaces and networks is very similar""" @@ -540,6 +589,43 @@ class NetworkTestCase_v6(BaseTestCase, NetmaskTestMixin_v6): factory = ipaddress.IPv6Network + def test_subnet_of(self): + # containee left of container + self.assertFalse( + self.factory('2000:999::/56').subnet_of( + self.factory('2000:aaa::/48'))) + # containee inside container + self.assertTrue( + self.factory('2000:aaa::/56').subnet_of( + self.factory('2000:aaa::/48'))) + # containee right of container + self.assertFalse( + self.factory('2000:bbb::/56').subnet_of( + self.factory('2000:aaa::/48'))) + # containee larger than container + self.assertFalse( + self.factory('2000:aaa::/48').subnet_of( + self.factory('2000:aaa::/56'))) + + def test_supernet_of(self): + # containee left of container + self.assertFalse( + self.factory('2000:999::/56').supernet_of( + self.factory('2000:aaa::/48'))) + # containee inside container + self.assertFalse( + self.factory('2000:aaa::/56').supernet_of( + self.factory('2000:aaa::/48'))) + # containee right of container + self.assertFalse( + self.factory('2000:bbb::/56').supernet_of( + self.factory('2000:aaa::/48'))) + # containee larger than container + self.assertTrue( + self.factory('2000:aaa::/48').supernet_of( + self.factory('2000:aaa::/56'))) + + class FactoryFunctionErrors(BaseTestCase):