Skip to content

Commit fadb785

Browse files
777GE90Faisal Mahmoodorsenthil
authored
bpo-42861: Add next_network function to ipaddress library (#24180)
Add `next_network` method that returns the next closest network of prefix size *next_prefix*. to Lib.ipaddress --------- Co-authored-by: Faisal Mahmood <killerklient@hotmail.co.uk> Co-authored-by: Senthil Kumaran <senthil@python.org>
1 parent 8615062 commit fadb785

5 files changed

Lines changed: 122 additions & 0 deletions

File tree

Doc/library/ipaddress.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,35 @@ dictionaries.
718718
.. deprecated:: 3.7
719719
It uses the same ordering and comparison algorithm as "<", "==", and ">"
720720

721+
.. method:: next_network(next_prefix=None)
722+
723+
Finds the next closest network of prefix size *next_prefix*. If
724+
*next_prefix=None*, then the current network prefix will be used.
725+
Returns a single network object.
726+
727+
Raises :exc:`ValueError` if *next_prefix* is out of range or no further
728+
network of the requested size exists.
729+
730+
>>> IPv4Network('192.0.2.0/24').next_network()
731+
IPv4Network('192.0.3.0/24')
732+
>>> IPv4Network('192.0.2.0/24').next_network(next_prefix=25)
733+
IPv4Network('192.0.3.0/25')
734+
>>> IPv4Network('192.0.2.0/24').next_network(next_prefix=23)
735+
IPv4Network('192.0.4.0/23')
736+
>>> IPv4Network('192.0.80.0/22').next_network(next_prefix=18)
737+
IPv4Network('192.0.128.0/18')
738+
>>> IPv4Network('192.0.80.0/22').next_network(next_prefix=50)
739+
Traceback (most recent call last):
740+
File "<stdin>", line 1, in <module>
741+
raise ValueError(
742+
ValueError: next prefix must be between 1 and 32
743+
>>> IPv4Network('255.255.255.0/24').next_network()
744+
Traceback (most recent call last):
745+
File "<stdin>", line 1, in <module>
746+
raise ValueError(
747+
ValueError: out of address space, cannot make another /24 network
748+
749+
.. versionadded:: 3.16
721750

722751
.. class:: IPv6Network(address, strict=True)
723752

@@ -790,6 +819,7 @@ dictionaries.
790819
.. method:: supernet(prefixlen_diff=1, new_prefix=None)
791820
.. method:: subnet_of(other)
792821
.. method:: supernet_of(other)
822+
.. method:: next_network(next_prefix=None)
793823
.. method:: compare_networks(other)
794824

795825
Refer to the corresponding attribute documentation in

Doc/whatsnew/3.16.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,14 @@ imaplib
253253
(Contributed by Przemysław Buczkowski and Serhiy Storchaka in :gh:`89869`.)
254254

255255

256+
ipaddress
257+
---------
258+
259+
Add :meth:`~ipaddress.IPv4Network.next_network` and
260+
:meth:`~ipaddress.IPv6Network.next_network` methods to find the next nearest
261+
network with a specific prefix size.
262+
263+
256264
logging
257265
-------
258266

Lib/ipaddress.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,47 @@ def is_loopback(self):
11191119
return (self.network_address.is_loopback and
11201120
self.broadcast_address.is_loopback)
11211121

1122+
def next_network(self, next_prefix=None):
1123+
"""Get the next closest network with a specific prefix.
1124+
1125+
Args:
1126+
next_prefix: The desired next prefix length, if not specified the
1127+
same self.prefixlen will be used
1128+
1129+
Returns:
1130+
An IPv(4|6) Network object of the next closest network.
1131+
1132+
"""
1133+
if next_prefix is None:
1134+
next_prefix = self.prefixlen
1135+
new_netmask = self.netmask
1136+
else:
1137+
if next_prefix < 1 or next_prefix > self.max_prefixlen:
1138+
raise ValueError(
1139+
f"next prefix must be between 1 and {self.max_prefixlen}"
1140+
)
1141+
new_netmask, _ = self._make_netmask(next_prefix)
1142+
1143+
bit_shift = (
1144+
self.max_prefixlen - next_prefix
1145+
if next_prefix <= self.prefixlen
1146+
else self.max_prefixlen - self.prefixlen
1147+
)
1148+
1149+
next_ip = (
1150+
((new_netmask._ip & self.network_address._ip) >> bit_shift) + 1
1151+
) << bit_shift
1152+
1153+
try:
1154+
return self.__class__(
1155+
f"{self._string_from_ip_int(next_ip)}/{next_prefix}"
1156+
)
1157+
except OverflowError:
1158+
raise ValueError(
1159+
f"out of address space, cannot make another /{next_prefix} "
1160+
"network"
1161+
) from None
1162+
11221163

11231164
class _BaseConstants:
11241165

Lib/test/test_ipaddress.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,47 @@ def testHosts(self):
15591559
self.assertEqual(list(ipaddress.ip_network(str_args).hosts()),
15601560
list(ipaddress.ip_network(tpl_args).hosts()))
15611561

1562+
def testNextNetwork(self):
1563+
ipv4 = ipaddress.IPv4Network('1.2.3.0/24')
1564+
self.assertEqual(
1565+
ipv4.next_network(),
1566+
ipaddress.IPv4Network('1.2.4.0/24'),
1567+
)
1568+
self.assertEqual(
1569+
ipv4.next_network(next_prefix=16),
1570+
ipaddress.IPv4Network('1.3.0.0/16'),
1571+
)
1572+
self.assertEqual(
1573+
ipv4.next_network(next_prefix=25),
1574+
ipaddress.IPv4Network('1.2.4.0/25'),
1575+
)
1576+
1577+
ipv6 = ipaddress.IPv6Network('2001:dbb8:aaaa:aaaa::/64')
1578+
self.assertEqual(
1579+
ipv6.next_network(),
1580+
ipaddress.IPv6Network('2001:dbb8:aaaa:aaab::/64'),
1581+
)
1582+
self.assertEqual(
1583+
ipv6.next_network(next_prefix=48),
1584+
ipaddress.IPv6Network('2001:dbb8:aaab::/48'),
1585+
)
1586+
self.assertEqual(
1587+
ipv6.next_network(next_prefix=88),
1588+
ipaddress.IPv6Network('2001:dbb8:aaaa:aaab::/88'),
1589+
)
1590+
1591+
def testNextNetworkWithBadPrefix(self):
1592+
self.assertRaises(ValueError, self.ipv4_network.next_network, 0)
1593+
self.assertRaises(ValueError, self.ipv4_network.next_network, 35)
1594+
self.assertRaises(ValueError, self.ipv6_network.next_network, 0)
1595+
self.assertRaises(ValueError, self.ipv6_network.next_network, 150)
1596+
1597+
def testNextNetworkOutOfAddressSpace(self):
1598+
ipv4 = ipaddress.IPv4Network('255.255.255.0/24')
1599+
self.assertRaises(ValueError, ipv4.next_network)
1600+
ipv6 = ipaddress.IPv6Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112')
1601+
self.assertRaises(ValueError, ipv6.next_network)
1602+
15621603
def testFancySubnetting(self):
15631604
self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)),
15641605
sorted(self.ipv4_network.subnets(new_prefix=27)))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :meth:`~ipaddress.IPv4Network.next_network` and
2+
:meth:`~ipaddress.IPv6Network.next_network`. Patch by Faisal Mahmood.

0 commit comments

Comments
 (0)