From 020f5c47d8137802d0f058ab2ed2dcb939a2eb8f Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
 <31488909+miss-islington@users.noreply.github.com>
Date: Tue, 3 May 2022 05:34:50 -0700
Subject: [PATCH] bpo-46415: Use f-string for ValueError in
 ipaddress.ip_{address,network,interface} helper functions (GH-30642)

`IPv*Network` and `IPv*Interface` constructors accept a 2-tuple of
(address description, netmask) as the address parameter.
When the tuple-based address is used errors are not propagated
correctly through the `ipaddress.ip_*` helper because of the %-formatting now expecting several arguments:

	In [7]: ipaddress.ip_network(("192.168.100.0", "fooo"))
        ...
	TypeError: not all arguments converted during string formatting

Compared to:

	In [8]: ipaddress.IPv4Network(("192.168.100.0", "foo"))
        ...
	NetmaskValueError: 'foo' is not a valid netmask

Use an f-string to make sure the error is always properly formatted.

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
(cherry picked from commit 52dc9c3066bcdc67a7a45d41cf158ecb1434d5f3)

Co-authored-by: Thomas Cellerier <thomascellerier@gmail.com>
---
 Lib/ipaddress.py                                 | 15 ++++++---------
 Lib/test/test_ipaddress.py                       | 16 ++++++++++++++++
 .../2022-01-17-16-53-30.bpo-46415.6wSYg-.rst     |  2 ++
 3 files changed, 24 insertions(+), 9 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Library/2022-01-17-16-53-30.bpo-46415.6wSYg-.rst

diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index 6cb92ed5520..25f373a06a2 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -50,8 +50,7 @@ def ip_address(address):
     except (AddressValueError, NetmaskValueError):
         pass
 
-    raise ValueError('%r does not appear to be an IPv4 or IPv6 address' %
-                     address)
+    raise ValueError(f'{address!r} does not appear to be an IPv4 or IPv6 address')
 
 
 def ip_network(address, strict=True):
@@ -80,8 +79,7 @@ def ip_network(address, strict=True):
     except (AddressValueError, NetmaskValueError):
         pass
 
-    raise ValueError('%r does not appear to be an IPv4 or IPv6 network' %
-                     address)
+    raise ValueError(f'{address!r} does not appear to be an IPv4 or IPv6 network')
 
 
 def ip_interface(address):
@@ -115,8 +113,7 @@ def ip_interface(address):
     except (AddressValueError, NetmaskValueError):
         pass
 
-    raise ValueError('%r does not appear to be an IPv4 or IPv6 interface' %
-                     address)
+    raise ValueError(f'{address!r} does not appear to be an IPv4 or IPv6 interface')
 
 
 def v4_int_to_packed(address):
@@ -159,7 +156,7 @@ def _split_optional_netmask(address):
     """Helper to split the netmask and raise AddressValueError if needed"""
     addr = str(address).split('/')
     if len(addr) > 2:
-        raise AddressValueError("Only one '/' permitted in %r" % address)
+        raise AddressValueError(f"Only one '/' permitted in {address!r}")
     return addr
 
 
@@ -1303,7 +1300,7 @@ def __init__(self, address):
         # which converts into a formatted IP string.
         addr_str = str(address)
         if '/' in addr_str:
-            raise AddressValueError("Unexpected '/' in %r" % address)
+            raise AddressValueError(f"Unexpected '/' in {address!r}")
         self._ip = self._ip_int_from_string(addr_str)
 
     @property
@@ -1912,7 +1909,7 @@ def __init__(self, address):
         # which converts into a formatted IP string.
         addr_str = str(address)
         if '/' in addr_str:
-            raise AddressValueError("Unexpected '/' in %r" % address)
+            raise AddressValueError(f"Unexpected '/' in {address!r}")
         addr_str, self._scope_id = self._split_scope_id(addr_str)
 
         self._ip = self._ip_int_from_string(addr_str)
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index cdd9880c3c1..22441274934 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -1133,6 +1133,14 @@ def testIPv4Tuple(self):
         self.assertEqual(ipaddress.IPv4Interface((3221225985, 24)),
                          ipaddress.IPv4Interface('192.0.2.1/24'))
 
+        # Invalid netmask
+        with self.assertRaises(ValueError):
+            ipaddress.IPv4Network(('192.0.2.1', '255.255.255.255.0'))
+
+        # Invalid netmask using factory
+        with self.assertRaises(ValueError):
+            ipaddress.ip_network(('192.0.2.1', '255.255.255.255.0'))
+
     # issue #16531: constructing IPv6Network from an (address, mask) tuple
     def testIPv6Tuple(self):
         # /128
@@ -1192,6 +1200,14 @@ def testIPv6Tuple(self):
             ipaddress.IPv6Network((ip_scoped, 96))
         # strict=False and host bits set
 
+        # Invalid netmask
+        with self.assertRaises(ValueError):
+            ipaddress.IPv6Network(('2001:db8::1', '255.255.255.0'))
+
+        # Invalid netmask using factory
+        with self.assertRaises(ValueError):
+            ipaddress.ip_network(('2001:db8::1', '255.255.255.0'))
+
     # issue57
     def testAddressIntMath(self):
         self.assertEqual(ipaddress.IPv4Address('1.1.1.1') + 255,
diff --git a/Misc/NEWS.d/next/Library/2022-01-17-16-53-30.bpo-46415.6wSYg-.rst b/Misc/NEWS.d/next/Library/2022-01-17-16-53-30.bpo-46415.6wSYg-.rst
new file mode 100644
index 00000000000..016d6656041
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-01-17-16-53-30.bpo-46415.6wSYg-.rst
@@ -0,0 +1,2 @@
+Fix ipaddress.ip_{address,interface,network} raising TypeError instead of
+ValueError if given invalid tuple as address parameter.
-- 
GitLab