From 30fb4b7195b9118f868c427d44de5e3c4544420a Mon Sep 17 00:00:00 2001 From: Oliver Giles Date: Thu, 13 Aug 2026 18:28:06 +1200 Subject: [PATCH] gh-88178: Fix exploded IPv6 addresses with a scope ID (GH-25824) IPv6Address.exploded raised AddressValueError, and IPv6Interface.exploded omitted the scope ID. (cherry picked from commit a2104b7ce0a154a7c24ada33ff8c298f48156bd2) Co-authored-by: Oliver Giles Co-authored-by: Serhiy Storchaka Co-authored-by: Claude Opus 5 (1M context) --- Lib/ipaddress.py | 14 +++++++++----- Lib/test/test_ipaddress.py | 6 ++++++ .../2021-06-03-22-23-38.bpo-44012.BK3HfA.rst | 5 +++++ 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-06-03-22-23-38.bpo-44012.BK3HfA.rst diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index f1062a8cd052a5..198dcd6de32a44 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1870,14 +1870,18 @@ def _explode_shorthand_ip_string(self): elif isinstance(self, IPv6Interface): ip_str = str(self.ip) else: - ip_str = str(self) + ip_str = self._string_from_ip_int(self._ip) ip_int = self._ip_int_from_string(ip_str) hex_str = '%032x' % ip_int - parts = [hex_str[x:x+4] for x in range(0, 32, 4)] - if isinstance(self, (_BaseNetwork, IPv6Interface)): - return '%s/%d' % (':'.join(parts), self._prefixlen) - return ':'.join(parts) + exploded = ':'.join([hex_str[x:x+4] for x in range(0, 32, 4)]) + if isinstance(self, _BaseNetwork): + return '%s/%d' % (exploded, self._prefixlen) + if self._scope_id: + exploded = '%s%%%s' % (exploded, self._scope_id) + if isinstance(self, IPv6Interface): + return '%s/%d' % (exploded, self._prefixlen) + return exploded def _reverse_pointer(self): """Return the reverse DNS pointer name for the IPv6 address. diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 3f017b97dc28a3..8cb1e6549737cf 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -2677,6 +2677,12 @@ def testExplodeShortHandIpStr(self): addr1.exploded) self.assertEqual('0000:0000:0000:0000:0000:0000:0000:0001/128', ipaddress.IPv6Interface('::1/128').exploded) + self.assertEqual('fe80:0000:0000:0000:0000:0000:0000:0001%1', + ipaddress.IPv6Address('fe80::1%1').exploded) + self.assertEqual('fe80:0000:0000:0000:0000:0000:0000:0001%eth0', + ipaddress.IPv6Address('fe80::1%eth0').exploded) + self.assertEqual('fe80:0000:0000:0000:0000:0000:0000:0001%1/64', + ipaddress.IPv6Interface('fe80::1%1/64').exploded) # issue 77 self.assertEqual('2001:0000:5ef5:79fd:0000:059d:a0e5:0ba1', addr2.exploded) diff --git a/Misc/NEWS.d/next/Library/2021-06-03-22-23-38.bpo-44012.BK3HfA.rst b/Misc/NEWS.d/next/Library/2021-06-03-22-23-38.bpo-44012.BK3HfA.rst new file mode 100644 index 00000000000000..28c21c327504bd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-06-03-22-23-38.bpo-44012.BK3HfA.rst @@ -0,0 +1,5 @@ +The ``exploded`` attribute of :class:`ipaddress.IPv6Address` and +:class:`ipaddress.IPv6Interface` now supports addresses with a scope ID +(link-local addresses). +Previously the former raised :exc:`~ipaddress.AddressValueError` +and the latter omitted the scope ID.