FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-87027: Add prev_network function to ipaddress library (follow-up to GH-24180) by 777GE90 · Pull Request #154041 · python/cpython · GitHub

Repository navigation

Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (2) .rst  (3) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
33 changes: 32 additions & 1 deletion Doc/library/ipaddress.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ dictionaries.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise ValueError(
ValueError: next prefix must be between 1 and 32
ValueError: 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>
Expand All @@ -748,6 +748,36 @@ dictionaries.

.. versionadded:: 3.16

.. method:: prev_network(prev_prefix=None)

Finds the previous closest network of prefix size *prev_prefix*. If
*prev_prefix=None*, then the current network prefix will be used.
Returns a single network object.

Raises :exc:`ValueError` if *prev_prefix* is out of range or no further
network of the requested size exists.

>>> IPv4Network('192.0.2.0/24').prev_network()
IPv4Network('192.0.1.0/24')
>>> IPv4Network('192.0.2.0/24').prev_network(prev_prefix=25)
IPv4Network('192.0.1.128/25')
>>> IPv4Network('192.0.2.0/24').prev_network(prev_prefix=23)
IPv4Network('192.0.0.0/23')
>>> IPv4Network('192.0.80.0/22').prev_network(prev_prefix=18)
IPv4Network('192.0.0.0/18')
>>> IPv4Network('192.0.80.0/22').prev_network(prev_prefix=50)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise ValueError(
ValueError: prefix must be between 1 and 32
>>> IPv4Network('0.0.0.0/24').prev_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)

Construct an IPv6 network definition. *address* can be one of the following:
Expand Down Expand Up @@ -820,6 +850,7 @@ dictionaries.
.. method:: subnet_of(other)
.. method:: supernet_of(other)
.. method:: next_network(next_prefix=None)
.. method:: prev_network(prev_prefix=None)
.. method:: compare_networks(other)

Refer to the corresponding attribute documentation in
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.16.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ ipaddress
and :meth:`IPv6Network.next_network() <ipaddress.IPv6Network.next_network>`
methods to find the next nearest network with a specific prefix size.
(Contributed by Faisal Mahmood in :gh:`87027`.)
* Add :meth:`IPv4Network.prev_network() <ipaddress.IPv4Network.prev_network>`
and :meth:`IPv6Network.prev_network() <ipaddress.IPv6Network.prev_network>`
methods to find the previous nearest network with a specific prefix size.
(Contributed by Faisal Mahmood in :gh:`87027`.)


logging
Expand Down
76 changes: 55 additions & 21 deletions Lib/ipaddress.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,46 @@ def is_loopback(self):
return (self.network_address.is_loopback and
self.broadcast_address.is_loopback)

def _network_by_offset(self, prefix, offset):
"""Get the network offset from this network.

Args:
prefix: Target prefix length.
offset: Number of networks to move (+1 or -1).

Returns:
An IPv(4|6) Network object.

"""
if prefix < 1 or prefix > self.max_prefixlen:
raise ValueError(
f"prefix must be between 1 and {self.max_prefixlen}"
)
new_netmask, _ = self._make_netmask(prefix)
error = f"out of address space, cannot make another /{prefix} network"

if prefix > self.prefixlen:
if offset < 0:
new_ip = self.network_address._ip - 1
else:
new_ip = self.broadcast_address._ip + 1
if new_ip < 0 or new_ip > self._ALL_ONES:
raise ValueError(error)

new_ip &= new_netmask._ip
else:
bit_shift = self.max_prefixlen - prefix
new_ip = (
((new_netmask._ip & self.network_address._ip) >> bit_shift)
+ offset
) << bit_shift
if not 0 <= new_ip <= self._ALL_ONES:
raise ValueError(error)

return self.__class__(
f"{self._string_from_ip_int(new_ip)}/{prefix}"
)

def next_network(self, next_prefix=None):
"""Get the next closest network with a specific prefix.

Expand All @@ -1136,31 +1176,25 @@ def next_network(self, next_prefix=None):
"""
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
)
return self._network_by_offset(next_prefix, 1)

next_ip = (
((new_netmask._ip & self.network_address._ip) >> bit_shift) + 1
) << bit_shift

if next_ip > self._ALL_ONES:
raise ValueError(
f"out of address space, cannot make another /{next_prefix} "
"network"
)
def prev_network(self, prev_prefix=None):
"""Get the previous closest network with a specific prefix.

Args:
prev_prefix: The desired previous prefix length, if not specified the
same self.prefixlen will be used

Returns:
An IPv(4|6) Network object of the previous closest network.

"""
if prev_prefix is None:
prev_prefix = self.prefixlen

return self.__class__((next_ip, next_prefix))
return self._network_by_offset(prev_prefix, -1)


class _BaseConstants:
Expand Down
69 changes: 61 additions & 8 deletions Lib/test/test_ipaddress.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1595,16 +1595,69 @@ def testNextNetworkWithBadPrefix(self):
self.assertRaises(ValueError, self.ipv6_network.next_network, 150)

def testNextNetworkOutOfAddressSpace(self):
error = r"out of address space, cannot make another /{} network"
ipv4 = ipaddress.IPv4Network('255.255.255.0/24')
self.assertRaisesRegex(
ValueError,
'out of address space, cannot make another /24 network',
ipv4.next_network)
with self.assertRaisesRegex(ValueError, error.format(24)):
ipv4.next_network()
with self.assertRaisesRegex(ValueError, error.format(25)):
ipv4.next_network(25)
ipv6 = ipaddress.IPv6Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112')
self.assertRaisesRegex(
ValueError,
'out of address space, cannot make another /112 network',
ipv6.next_network)
with self.assertRaisesRegex(ValueError, error.format(112)):
ipv6.next_network()
with self.assertRaisesRegex(ValueError, error.format(120)):
ipv6.next_network(120)

def testPrevNetwork(self):
ipv4 = ipaddress.IPv4Network('1.2.3.0/24')
self.assertEqual(
ipv4.prev_network(),
ipaddress.IPv4Network('1.2.2.0/24'),
)
self.assertEqual(
ipv4.prev_network(prev_prefix=16),
ipaddress.IPv4Network('1.1.0.0/16'),
)
self.assertEqual(
ipv4.prev_network(prev_prefix=25),
ipaddress.IPv4Network('1.2.2.128/25'),
)
self.assertEqual(
ipv4.prev_network(prev_prefix=23),
ipaddress.IPv4Network('1.2.0.0/23'),
)

ipv6 = ipaddress.IPv6Network('2001:dbb8:aaaa:aaaa::/64')
self.assertEqual(
ipv6.prev_network(),
ipaddress.IPv6Network('2001:dbb8:aaaa:aaa9::/64'),
)
self.assertEqual(
ipv6.prev_network(prev_prefix=48),
ipaddress.IPv6Network('2001:dbb8:aaa9::/48'),
)
self.assertEqual(
ipv6.prev_network(prev_prefix=88),
ipaddress.IPv6Network('2001:dbb8:aaaa:aaa9:ffff:ff00::/88'),
)

def testPrevNetworkWithBadPrefix(self):
self.assertRaises(ValueError, self.ipv4_network.prev_network, 0)
self.assertRaises(ValueError, self.ipv4_network.prev_network, 35)
self.assertRaises(ValueError, self.ipv6_network.prev_network, 0)
self.assertRaises(ValueError, self.ipv6_network.prev_network, 150)

def testPrevNetworkOutOfAddressSpace(self):
error = r"out of address space, cannot make another /{} network"
ipv4 = ipaddress.IPv4Network('0.0.0.0/24')
with self.assertRaisesRegex(ValueError, error.format(24)):
ipv4.prev_network()
with self.assertRaisesRegex(ValueError, error.format(25)):
ipv4.prev_network(25)
ipv6 = ipaddress.IPv6Network('::/112')
with self.assertRaisesRegex(ValueError, error.format(112)):
ipv6.prev_network()
with self.assertRaisesRegex(ValueError, error.format(120)):
ipv6.prev_network(120)

def testFancySubnetting(self):
self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)),
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :meth:`~ipaddress.IPv4Network.prev_network` and
:meth:`~ipaddress.IPv6Network.prev_network`. Patch by Faisal Mahmood.
Loading

Back | FazBrowse Home | New Git URL