Skip to content

Commit a8f3481

Browse files
committed
bpo-42861: Added prev_network method, required refactor of next_network to prevent duplication
1 parent a1d5804 commit a8f3481

2 files changed

Lines changed: 109 additions & 24 deletions

File tree

Lib/ipaddress.py

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

1122+
def _network_by_offset(self, prefix, offset):
1123+
"""Get the network offset from this network.
1124+
1125+
Args:
1126+
prefix: Target prefix length.
1127+
offset: Number of networks to move (+1 or -1).
1128+
1129+
Returns:
1130+
An IPv(4|6) Network object.
1131+
1132+
"""
1133+
if prefix < 1 or prefix > self.max_prefixlen:
1134+
raise ValueError(
1135+
f"prefix must be between 1 and {self.max_prefixlen}"
1136+
)
1137+
new_netmask, _ = self._make_netmask(prefix)
1138+
error = f"out of address space, cannot make another /{prefix} network"
1139+
1140+
if prefix > self.prefixlen:
1141+
if offset < 0:
1142+
new_ip = self.network_address._ip - 1
1143+
else:
1144+
new_ip = self.broadcast_address._ip + 1
1145+
if new_ip < 0 or new_ip > self._ALL_ONES:
1146+
raise ValueError(error)
1147+
1148+
new_ip &= new_netmask._ip
1149+
else:
1150+
bit_shift = self.max_prefixlen - prefix
1151+
new_ip = (
1152+
((new_netmask._ip & self.network_address._ip) >> bit_shift)
1153+
+ offset
1154+
) << bit_shift
1155+
1156+
if new_ip < 0:
1157+
raise ValueError(error)
1158+
1159+
try:
1160+
return self.__class__(
1161+
f"{self._string_from_ip_int(new_ip)}/{prefix}"
1162+
)
1163+
except OverflowError:
1164+
raise ValueError(error) from None
1165+
11221166
def next_network(self, next_prefix=None):
11231167
"""Get the next closest network with a specific prefix.
11241168
@@ -1132,33 +1176,25 @@ def next_network(self, next_prefix=None):
11321176
"""
11331177
if next_prefix is None:
11341178
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)
11421179

1143-
bit_shift = (
1144-
self.max_prefixlen - next_prefix
1145-
if next_prefix <= self.prefixlen
1146-
else self.max_prefixlen - self.prefixlen
1147-
)
1180+
return self._network_by_offset(next_prefix, 1)
11481181

1149-
next_ip = (
1150-
((new_netmask._ip & self.network_address._ip) >> bit_shift) + 1
1151-
) << bit_shift
11521182

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
1183+
def prev_network(self, prev_prefix=None):
1184+
"""Get the previous closest network with a specific prefix.
1185+
1186+
Args:
1187+
prev_prefix: The desired previous prefix length, if not specified the
1188+
same self.prefixlen will be used
1189+
1190+
Returns:
1191+
An IPv(4|6) Network object of the previous closest network.
1192+
1193+
"""
1194+
if prev_prefix is None:
1195+
prev_prefix = self.prefixlen
1196+
1197+
return self._network_by_offset(prev_prefix, -1)
11621198

11631199

11641200
class _BaseConstants:

Lib/test/test_ipaddress.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,8 +1597,57 @@ def testNextNetworkWithBadPrefix(self):
15971597
def testNextNetworkOutOfAddressSpace(self):
15981598
ipv4 = ipaddress.IPv4Network('255.255.255.0/24')
15991599
self.assertRaises(ValueError, ipv4.next_network)
1600+
self.assertRaises(ValueError, ipv4.next_network, 25)
16001601
ipv6 = ipaddress.IPv6Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112')
16011602
self.assertRaises(ValueError, ipv6.next_network)
1603+
self.assertRaises(ValueError, ipv4.next_network, 112)
1604+
1605+
def testPrevNetwork(self):
1606+
ipv4 = ipaddress.IPv4Network('1.2.3.0/24')
1607+
self.assertEqual(
1608+
ipv4.prev_network(),
1609+
ipaddress.IPv4Network('1.2.2.0/24'),
1610+
)
1611+
self.assertEqual(
1612+
ipv4.prev_network(prev_prefix=16),
1613+
ipaddress.IPv4Network('1.1.0.0/16'),
1614+
)
1615+
self.assertEqual(
1616+
ipv4.prev_network(prev_prefix=25),
1617+
ipaddress.IPv4Network('1.2.2.128/25'),
1618+
)
1619+
self.assertEqual(
1620+
ipv4.prev_network(prev_prefix=23),
1621+
ipaddress.IPv4Network('1.2.0.0/23'),
1622+
)
1623+
1624+
ipv6 = ipaddress.IPv6Network('2001:dbb8:aaaa:aaaa::/64')
1625+
self.assertEqual(
1626+
ipv6.prev_network(),
1627+
ipaddress.IPv6Network('2001:dbb8:aaaa:aaa9::/64'),
1628+
)
1629+
self.assertEqual(
1630+
ipv6.prev_network(prev_prefix=48),
1631+
ipaddress.IPv6Network('2001:dbb8:aaa9::/48'),
1632+
)
1633+
self.assertEqual(
1634+
ipv6.prev_network(prev_prefix=88),
1635+
ipaddress.IPv6Network('2001:dbb8:aaaa:aaa9:ffff:ff00::/88'),
1636+
)
1637+
1638+
def testPrevNetworkWithBadPrefix(self):
1639+
self.assertRaises(ValueError, self.ipv4_network.prev_network, 0)
1640+
self.assertRaises(ValueError, self.ipv4_network.prev_network, 35)
1641+
self.assertRaises(ValueError, self.ipv6_network.prev_network, 0)
1642+
self.assertRaises(ValueError, self.ipv6_network.prev_network, 150)
1643+
1644+
def testPrevNetworkOutOfAddressSpace(self):
1645+
ipv4 = ipaddress.IPv4Network('0.0.0.0/24')
1646+
self.assertRaises(ValueError, ipv4.prev_network)
1647+
self.assertRaises(ValueError, ipv4.prev_network, 25)
1648+
ipv6 = ipaddress.IPv6Network('::/112')
1649+
self.assertRaises(ValueError, ipv6.prev_network)
1650+
self.assertRaises(ValueError, ipv6.prev_network, 112)
16021651

16031652
def testFancySubnetting(self):
16041653
self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)),

0 commit comments

Comments
 (0)