summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCheryl Sabella <cheryl.sabella@gmail.com>2017-10-22 21:39:49 (GMT)
committerAntoine Pitrou <pitrou@free.fr>2017-10-22 21:39:49 (GMT)
commit91dc64ba3f51100540b2ab6c6cd72c3bb18a6d49 (patch)
tree011cf75c270c0a1ba07a023caa0c2f1a547e0d86
parent04e36af9b88cd5e80fc818e51d46f07252a2ff3f (diff)
downloadcpython-91dc64ba3f51100540b2ab6c6cd72c3bb18a6d49.zip
cpython-91dc64ba3f51100540b2ab6c6cd72c3bb18a6d49.tar.gz
cpython-91dc64ba3f51100540b2ab6c6cd72c3bb18a6d49.tar.bz2
bpo-20825: Containment test for ip_network in ip_network.
-rw-r--r--Doc/library/ipaddress.rst24
-rw-r--r--Lib/ipaddress.py29
-rw-r--r--Lib/test/test_ipaddress.py87
-rw-r--r--Misc/NEWS.d/next/Library/2017-10-21-09-13-16.bpo-20825.-1MBEy.rst3
4 files changed, 136 insertions, 7 deletions
diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst
index 36c6859..75c9107 100644
--- a/Doc/library/ipaddress.rst
+++ b/Doc/library/ipaddress.rst
@@ -543,6 +543,28 @@ so to avoid duplication they are only documented for :class:`IPv4Network`.
>>> 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.7
+
+ .. 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.7
+
.. method:: compare_networks(other)
Compare this network to *other*. In this comparison only the network
@@ -621,6 +643,8 @@ so to avoid duplication they are only documented for :class:`IPv4Network`.
.. 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 --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index 70746f8..e8ce4ce 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -776,8 +776,7 @@ class _BaseNetwork(_IPAddressBase):
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 @@ class _BaseNetwork(_IPAddressBase):
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,26 @@ class _BaseNetwork(_IPAddressBase):
return (self.network_address.is_multicast and
self.broadcast_address.is_multicast)
+ @staticmethod
+ def _is_subnet_of(a, b):
+ try:
+ # Always false if one is v4 and the other is v6.
+ if a._version != b._version:
+ raise TypeError(f"{a} and {b} are not of the same version")
+ return (b.network_address <= a.network_address and
+ b.broadcast_address >= a.broadcast_address)
+ except AttributeError:
+ raise TypeError(f"Unable to test subnet containment "
+ f"between {a} and {b}")
+
+ def subnet_of(self, other):
+ """Return True if this network is a subnet of other."""
+ return self._is_subnet_of(self, other)
+
+ def supernet_of(self, other):
+ """Return True if this network is a supernet of other."""
+ return self._is_subnet_of(other, self)
+
@property
def is_reserved(self):
"""Test if the address is otherwise IETF reserved.
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index 5d96330..dbf68b3 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -92,7 +92,6 @@ class CommonTestMixin:
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 InterfaceTestCase_v4(BaseTestCase, NetmaskTestMixin_v4):
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_subnet_of_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,42 @@ class InterfaceTestCase_v6(BaseTestCase, NetmaskTestMixin_v6):
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):
diff --git a/Misc/NEWS.d/next/Library/2017-10-21-09-13-16.bpo-20825.-1MBEy.rst b/Misc/NEWS.d/next/Library/2017-10-21-09-13-16.bpo-20825.-1MBEy.rst
new file mode 100644
index 0000000..c1d23ec
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-10-21-09-13-16.bpo-20825.-1MBEy.rst
@@ -0,0 +1,3 @@
+Add `subnet_of` and `superset_of` containment tests to
+:class:`ipaddress.IPv6Network` and :class:`ipaddress.IPv4Network`.
+Patch by Michel Albert and Cheryl Sabella.