]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-42861: Add `next_network` function to ipaddress library (#24180)
author777GE90 <faisal.mahmood@hotmail.co.uk>
Wed, 8 Jul 2026 14:06:55 +0000 (15:06 +0100)
committerGitHub <noreply@github.com>
Wed, 8 Jul 2026 14:06:55 +0000 (07:06 -0700)
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>
Doc/library/ipaddress.rst
Doc/whatsnew/3.16.rst
Lib/ipaddress.py
Lib/test/test_ipaddress.py
Misc/NEWS.d/next/Library/2021-01-09-18-40-15.bpo-42861.T7Ge9O.rst [new file with mode: 0644]

index 9ccd8602bcb2c337ff3defa1fa716fdcfb1230bb..387d8034bb8c84a8fd4391f8ed14344b27d11d30 100644 (file)
@@ -718,6 +718,35 @@ dictionaries.
       .. deprecated:: 3.7
          It uses the same ordering and comparison algorithm as "<", "==", and ">"
 
+   .. method:: next_network(next_prefix=None)
+
+      Finds the next closest network of prefix size *next_prefix*.  If
+      *next_prefix=None*, then the current network prefix will be used.
+      Returns a single network object.
+
+      Raises :exc:`ValueError` if *next_prefix* is out of range or no further
+      network of the requested size exists.
+
+         >>> IPv4Network('192.0.2.0/24').next_network()
+         IPv4Network('192.0.3.0/24')
+         >>> IPv4Network('192.0.2.0/24').next_network(next_prefix=25)
+         IPv4Network('192.0.3.0/25')
+         >>> IPv4Network('192.0.2.0/24').next_network(next_prefix=23)
+         IPv4Network('192.0.4.0/23')
+         >>> IPv4Network('192.0.80.0/22').next_network(next_prefix=18)
+         IPv4Network('192.0.128.0/18')
+         >>> IPv4Network('192.0.80.0/22').next_network(next_prefix=50)
+         Traceback (most recent call last):
+            File "<stdin>", line 1, in <module>
+               raise ValueError(
+         ValueError: next prefix must be between 1 and 32
+         >>> IPv4Network('255.255.255.0/24').next_network()
+         Traceback (most recent call last):
+            File "<stdin>", line 1, in <module>
+               raise ValueError(
+         ValueError: out of address space, cannot make another /24 network
+
+      .. versionadded:: 3.16
 
 .. class:: IPv6Network(address, strict=True)
 
@@ -790,6 +819,7 @@ dictionaries.
    .. method:: supernet(prefixlen_diff=1, new_prefix=None)
    .. method:: subnet_of(other)
    .. method:: supernet_of(other)
+   .. method:: next_network(next_prefix=None)
    .. method:: compare_networks(other)
 
       Refer to the corresponding attribute documentation in
index 7bf3cc3c22a59edfd41663edf0e0e60e565f31e5..e871ad822bbcedc563658e541e3732bf3c90893f 100644 (file)
@@ -253,6 +253,14 @@ imaplib
   (Contributed by Przemysław Buczkowski and Serhiy Storchaka in :gh:`89869`.)
 
 
+ipaddress
+---------
+
+Add :meth:`~ipaddress.IPv4Network.next_network` and
+:meth:`~ipaddress.IPv6Network.next_network` methods to find the next nearest
+network with a specific prefix size.
+
+
 logging
 -------
 
index f1062a8cd052a5503ba6ed40eccc2ee17ebe9872..9c7a0a46f4f0e1f6a1ada2a81f4ea8cd6ee39668 100644 (file)
@@ -1119,6 +1119,47 @@ class _BaseNetwork(_IPAddressBase):
         return (self.network_address.is_loopback and
                 self.broadcast_address.is_loopback)
 
+    def next_network(self, next_prefix=None):
+        """Get the next closest network with a specific prefix.
+
+        Args:
+            next_prefix: The desired next prefix length, if not specified the
+            same self.prefixlen will be used
+
+        Returns:
+            An IPv(4|6) Network object of the next closest network.
+
+        """
+        if next_prefix is None:
+            next_prefix = self.prefixlen
+            new_netmask = self.netmask
+        else:
+            if next_prefix < 1 or next_prefix > self.max_prefixlen:
+                raise ValueError(
+                    f"next prefix must be between 1 and {self.max_prefixlen}"
+                )
+            new_netmask, _ = self._make_netmask(next_prefix)
+
+        bit_shift = (
+            self.max_prefixlen - next_prefix
+            if next_prefix <= self.prefixlen
+            else self.max_prefixlen - self.prefixlen
+        )
+
+        next_ip = (
+            ((new_netmask._ip & self.network_address._ip) >> bit_shift) + 1
+        ) << bit_shift
+
+        try:
+            return self.__class__(
+                f"{self._string_from_ip_int(next_ip)}/{next_prefix}"
+            )
+        except OverflowError:
+            raise ValueError(
+                f"out of address space, cannot make another /{next_prefix} "
+                "network"
+            ) from None
+
 
 class _BaseConstants:
 
index 3f017b97dc28a384c1f9c36e526477bd90fe47e4..a74b692784eb59420d3eeaa0ba7cf14b401a80e1 100644 (file)
@@ -1559,6 +1559,47 @@ class IpaddrUnitTest(unittest.TestCase):
         self.assertEqual(list(ipaddress.ip_network(str_args).hosts()),
                          list(ipaddress.ip_network(tpl_args).hosts()))
 
+    def testNextNetwork(self):
+        ipv4 = ipaddress.IPv4Network('1.2.3.0/24')
+        self.assertEqual(
+            ipv4.next_network(),
+            ipaddress.IPv4Network('1.2.4.0/24'),
+        )
+        self.assertEqual(
+            ipv4.next_network(next_prefix=16),
+            ipaddress.IPv4Network('1.3.0.0/16'),
+        )
+        self.assertEqual(
+            ipv4.next_network(next_prefix=25),
+            ipaddress.IPv4Network('1.2.4.0/25'),
+        )
+
+        ipv6 = ipaddress.IPv6Network('2001:dbb8:aaaa:aaaa::/64')
+        self.assertEqual(
+            ipv6.next_network(),
+            ipaddress.IPv6Network('2001:dbb8:aaaa:aaab::/64'),
+        )
+        self.assertEqual(
+            ipv6.next_network(next_prefix=48),
+            ipaddress.IPv6Network('2001:dbb8:aaab::/48'),
+        )
+        self.assertEqual(
+            ipv6.next_network(next_prefix=88),
+            ipaddress.IPv6Network('2001:dbb8:aaaa:aaab::/88'),
+        )
+
+    def testNextNetworkWithBadPrefix(self):
+        self.assertRaises(ValueError, self.ipv4_network.next_network, 0)
+        self.assertRaises(ValueError, self.ipv4_network.next_network, 35)
+        self.assertRaises(ValueError, self.ipv6_network.next_network, 0)
+        self.assertRaises(ValueError, self.ipv6_network.next_network, 150)
+
+    def testNextNetworkOutOfAddressSpace(self):
+        ipv4 = ipaddress.IPv4Network('255.255.255.0/24')
+        self.assertRaises(ValueError, ipv4.next_network)
+        ipv6 = ipaddress.IPv6Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112')
+        self.assertRaises(ValueError, ipv6.next_network)
+
     def testFancySubnetting(self):
         self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)),
                          sorted(self.ipv4_network.subnets(new_prefix=27)))
diff --git a/Misc/NEWS.d/next/Library/2021-01-09-18-40-15.bpo-42861.T7Ge9O.rst b/Misc/NEWS.d/next/Library/2021-01-09-18-40-15.bpo-42861.T7Ge9O.rst
new file mode 100644 (file)
index 0000000..46ed99d
--- /dev/null
@@ -0,0 +1,2 @@
+Add :meth:`~ipaddress.IPv4Network.next_network` and
+:meth:`~ipaddress.IPv6Network.next_network`.  Patch by Faisal Mahmood.