diff -r 802ed5baffb3 Doc/library/ipaddress.rst --- a/Doc/library/ipaddress.rst Sat Mar 22 20:39:08 2014 +0100 +++ b/Doc/library/ipaddress.rst Sun Mar 23 16:27:02 2014 +0100 @@ -518,6 +518,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 *other* is a subnet of this network. + + >>> a = ip_network('192.168.1.0/24') + >>> b = ip_network('192.168.1.128/30') + >>> b.subnet_of(a) + True + + .. versionadded:: 3.5 + + .. method:: supernet_of(other) + + Returns *True* if *other* is a supernet of this network. + + >>> a = ip_network('192.168.1.0/24') + >>> b = ip_network('192.168.1.128/30') + >>> a.supernet_of(b) + True + + .. versionadded:: 3.5 + .. method:: compare_networks(other) Compare this network to *other*. In this comparison only the network @@ -584,6 +606,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 802ed5baffb3 Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst Sat Mar 22 20:39:08 2014 +0100 +++ b/Doc/whatsnew/3.5.rst Sun Mar 23 16:27:02 2014 +0100 @@ -137,6 +137,10 @@ * :class:`xmlrpc.client.ServerProxy` is now a :term:`context manager` (contributed by Claudiu Popa in :issue:`20627`). +* XXX Describe the _BaseNetwork.subnet_of(self, other) and + _BaseNetwork.subnet_of(self, other) functions added to the ipaddress module. + (Contributed by M. Albert in :issue:`20825`.) + Optimizations ============= diff -r 802ed5baffb3 Lib/ipaddress.py --- a/Lib/ipaddress.py Sat Mar 22 20:39:08 2014 +0100 +++ b/Lib/ipaddress.py Sun Mar 23 16:27:02 2014 +0100 @@ -291,8 +291,7 @@ if not ret_array: last_addr = cur_addr ret_array.append(cur_addr) - elif (cur_addr.network_address >= last_addr.network_address and - cur_addr.broadcast_address <= last_addr.broadcast_address): + elif cur_addr.subnet_of(last_addr): optimized = True elif cur_addr == list(last_addr.supernet().subnets())[1]: ret_array[-1] = last_addr = last_addr.supernet() @@ -794,8 +793,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: raise StopIteration @@ -806,12 +804,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: @@ -990,6 +986,34 @@ strict=False) return t.__class__('%s/%d' % (t.network_address, t.prefixlen)) + def subnet_of(self, other): + # always false if one is v4 and the other is v6. + if self._version != other._version: + return False + # dealing with another network. + if (hasattr(other, 'network_address') and + hasattr(other, 'broadcast_address')): + return (other.network_address <= self.network_address and + other.broadcast_address >= self.broadcast_address) + # dealing with another address + else: + raise TypeError('Unable to test subnet containment with element ' + 'of type %s' % type(other)) + + def supernet_of(self, other): + # always false if one is v4 and the other is v6. + if self._version != other._version: + return False + # dealing with another network. + if (hasattr(other, 'network_address') and + hasattr(other, 'broadcast_address')): + return (other.network_address >= self.network_address and + other.broadcast_address <= self.broadcast_address) + # dealing with another address + else: + raise TypeError('Unable to test subnet containment with element ' + 'of type %s' % type(other)) + @property def is_multicast(self): """Test if the address is reserved for multicast use. diff -r 802ed5baffb3 Lib/test/test_ipaddress.py --- a/Lib/test/test_ipaddress.py Sat Mar 22 20:39:08 2014 +0100 +++ b/Lib/test/test_ipaddress.py Sun Mar 23 16:27:02 2014 +0100 @@ -80,7 +80,6 @@ self.assertRaises(TypeError, hex, self.factory(1)) self.assertRaises(TypeError, bytes, self.factory(1)) - class CommonTestMixin_v4(CommonTestMixin): def test_leading_zeros(self): @@ -446,6 +445,42 @@ 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.0.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'))) + class NetmaskTestMixin_v6(CommonTestMixin_v6): """Input validation on interfaces and networks is very similar""" @@ -502,6 +537,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):