https://github.com/python/cpython/commit/a2104b7ce0a154a7c24ada33ff8c298f48156bd2
commit: a2104b7ce0a154a7c24ada33ff8c298f48156bd2
branch: main
author: Oliver Giles <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-13T06:28:06Z
summary:

gh-88178: Fix exploded IPv6 addresses with a scope ID (GH-25824)

IPv6Address.exploded raised AddressValueError, and
IPv6Interface.exploded omitted the scope ID.

Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2021-06-03-22-23-38.bpo-44012.BK3HfA.rst
M Lib/ipaddress.py
M Lib/test/test_ipaddress.py

diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index 9c7a0a46f4f0e1..6978483544e621 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -1911,14 +1911,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 a74b692784eb59..375d45172a9f4d 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -2718,6 +2718,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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to