diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 703fa289dda1fb..e804d979cfa936 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1547,6 +1547,30 @@ def __init__(self, address, strict=True): elif self._prefixlen == (self.max_prefixlen): self.hosts = lambda: [IPv4Address(addr)] + @property + def shorthand(self): + """ + Returns the shorthand representation of the IPv4 network. + + This method abbreviates the IPv4 network by removing trailing + zero octets from the network address. + + Returns: + str: The shorthand IPv4 network in the format 'X.X/X'. + + Example: + >>> network = IPv4Network('192.168.0.0/24') + >>> network.shorthand + '192.168/24' + """ + # Split the network address into octets + octets = str(self.network_address).split('.') + # Remove trailing zero octets + while octets and octets[-1] == '0': + octets.pop() + # Rejoin the remaining octets and append the prefix length + return '.'.join(octets) + f"/{self.prefixlen}" + @property @functools.lru_cache() def is_global(self): @@ -2341,6 +2365,24 @@ def hosts(self): for x in range(network + 1, broadcast + 1): yield self._address_class(x) + @property + def shorthand(self): + """ + Returns the shorthand representation of the IPv6 network. + + This method compresses the IPv6 address to its shortest form + and appends the prefix length. + + Returns: + str: The shorthand IPv6 network in the format 'X::/Y'. + + Example: + >>> network = IPv6Network('2001:db8:0:0:0:0:0:0/32') + >>> network.shorthand + '2001:db8::/32' + """ + return f"{self.network_address.compressed}/{self.prefixlen}" + @property def is_site_local(self): """Test if the address is reserved for site-local. diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index b1ac2b94f41b38..2bdab77f59e66e 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -709,6 +709,12 @@ def test_subnet_of_mixed_types(self): ipaddress.IPv6Network('::1/128').subnet_of( ipaddress.IPv4Network('10.0.0.0/30')) + def test_shorthand_ipv4(self): + self.assertEqual(ipaddress.IPv4Network("1.2.0.0/16").shorthand, "1.2/16") + self.assertEqual(ipaddress.IPv4Network("10.0.0.0/8").shorthand, "10/8") + self.assertEqual(ipaddress.IPv4Network("192.168.0.0/24").shorthand, "192.168/24") + self.assertEqual(ipaddress.IPv4Network("0.0.0.0/0").shorthand, "/0") + class NetmaskTestMixin_v6(CommonTestMixin_v6): """Input validation on interfaces and networks is very similar""" @@ -865,6 +871,13 @@ def test_supernet_of(self): self.factory('2000:aaa::/48').supernet_of( self.factory('2000:aaa::/56'))) + def test_shorthand_ipv6(self): + self.assertEqual(ipaddress.IPv6Network("2001:db8:0:0:0:0:0:0/32").shorthand, "2001:db8::/32") + self.assertEqual(ipaddress.IPv6Network("::/0").shorthand, "::/0") + self.assertEqual(ipaddress.IPv6Network("0:0:0:0:0:0:0:0/0").shorthand, "::/0") + self.assertEqual(ipaddress.IPv6Network("2001:db8:0:0:0:0:0:1/128").shorthand, "2001:db8::1/128") + self.assertEqual(ipaddress.IPv6Network("::1/128").shorthand, "::1/128") + class FactoryFunctionErrors(BaseTestCase): diff --git a/Misc/NEWS.d/next/Library/2025-01-14-05-46-29.gh-issue-128810.cj6Vhq.rst b/Misc/NEWS.d/next/Library/2025-01-14-05-46-29.gh-issue-128810.cj6Vhq.rst new file mode 100644 index 00000000000000..ef818993c13eac --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-14-05-46-29.gh-issue-128810.cj6Vhq.rst @@ -0,0 +1 @@ +Added a .shorthand function to IPv4Network and IPv6Network's in the ipaddress module to be able to display a prefix such as 1.2.0.0/16 as it's shorthand version (often used by network operators) as 1.2/16.