From d79f30226134aee2500e7107c2af65ece897ef41 Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Sun, 19 Jul 2026 17:32:45 +0300 Subject: [PATCH 1/2] gh-153769: Clarify the TypeError from ipaddress subnet_of()/supernet_of() Passing something that is not a network object, such as an address, to IPv4Network.subnet_of() or supernet_of() made _is_subnet_of() reach for the network_address attribute, fail with an AttributeError, and turn that into a TypeError with a message that read like a real containment result and hid the actual cause. Check that both arguments are network objects up front and raise a clear TypeError explaining what is required, so nothing masks an internal AttributeError anymore. --- Lib/ipaddress.py | 17 ++++++++--------- Lib/test/test_ipaddress.py | 14 ++++++++++++++ Misc/ACKS | 1 + ...26-07-19-16-30-00.gh-issue-153769.STygbq.rst | 5 +++++ 4 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-19-16-30-00.gh-issue-153769.STygbq.rst diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 9c7a0a46f4f0e1f..d791c5aaf9d5b50 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1026,15 +1026,14 @@ def is_multicast(self): @staticmethod def _is_subnet_of(a, b): - try: - # Always false if one is v4 and the other is v6. - if a.version != b.version: - raise TypeError(f"{a} and {b} are not of the same version") - return (b.network_address <= a.network_address and - b.broadcast_address >= a.broadcast_address) - except AttributeError: - raise TypeError(f"Unable to test subnet containment " - f"between {a} and {b}") + if not (isinstance(a, _BaseNetwork) and isinstance(b, _BaseNetwork)): + raise TypeError(f"Unable to test subnet containment between " + f"{a} and {b}: both must be network objects") + # Always false if one is v4 and the other is v6. + if a.version != b.version: + raise TypeError(f"{a} and {b} are not of the same version") + return (b.network_address <= a.network_address and + b.broadcast_address >= a.broadcast_address) def subnet_of(self, other): """Return True if this network is a subnet of other.""" diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index a74b692784eb594..73e954a2276d679 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -723,6 +723,20 @@ def test_subnet_of_mixed_types(self): ipaddress.IPv6Network('::1/128').subnet_of( ipaddress.IPv4Network('10.0.0.0/30')) + def test_subnet_of_non_network(self): + # Passing something that is not a network object (e.g. an address) + # must raise a clear TypeError rather than masking an internal + # AttributeError (gh-153769). + net = ipaddress.IPv4Network('10.0.0.0/30') + for other in (ipaddress.IPv4Address('10.0.0.1'), '10.0.0.0/30', 42): + for method in (net.subnet_of, net.supernet_of): + with self.assertRaises(TypeError) as cm: + method(other) + self.assertIn('network', str(cm.exception)) + # The error should not be a swallowed AttributeError. + self.assertNotIsInstance(cm.exception.__context__, + AttributeError) + class NetmaskTestMixin_v6(CommonTestMixin_v6): """Input validation on interfaces and networks is very similar""" diff --git a/Misc/ACKS b/Misc/ACKS index fec00c1b272f4ea..5fe1d8cd1121ee4 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -2008,6 +2008,7 @@ Wm. Keith van der Meulen Eric N. Vander Weele Andrew Vant Atul Varma +Vyron Vasileiadis Dmitry Vasiliev Sebastian Ortiz Vasquez Alexandre Vassalotti diff --git a/Misc/NEWS.d/next/Library/2026-07-19-16-30-00.gh-issue-153769.STygbq.rst b/Misc/NEWS.d/next/Library/2026-07-19-16-30-00.gh-issue-153769.STygbq.rst new file mode 100644 index 000000000000000..587ae0c912b98d8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-19-16-30-00.gh-issue-153769.STygbq.rst @@ -0,0 +1,5 @@ +:meth:`~ipaddress.IPv4Network.subnet_of` and +:meth:`~ipaddress.IPv4Network.supernet_of` now raise a clear +:exc:`TypeError` when passed an argument that is not a network object, +such as an address, instead of a confusing error that masked an internal +:exc:`AttributeError`. From b72a0ef44560cc6148ced27263f00e90b9e06c42 Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Thu, 6 Aug 2026 01:10:24 +0300 Subject: [PATCH 2/2] Only improve the message, keep the AttributeError handler An isinstance() check on _BaseNetwork rejects a network-like object that is not a subclass, which works today. Keeping the handler and suppressing the context keeps that working and still gives the caller one clear error. --- Lib/ipaddress.py | 18 ++++++++++-------- Lib/test/test_ipaddress.py | 5 ++--- ...6-07-19-16-30-00.gh-issue-153769.STygbq.rst | 7 +++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index d791c5aaf9d5b50..8f686c1c9e20f49 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1026,14 +1026,16 @@ def is_multicast(self): @staticmethod def _is_subnet_of(a, b): - if not (isinstance(a, _BaseNetwork) and isinstance(b, _BaseNetwork)): - raise TypeError(f"Unable to test subnet containment between " - f"{a} and {b}: both must be network objects") - # Always false if one is v4 and the other is v6. - if a.version != b.version: - raise TypeError(f"{a} and {b} are not of the same version") - return (b.network_address <= a.network_address and - b.broadcast_address >= a.broadcast_address) + try: + # Always false if one is v4 and the other is v6. + if a.version != b.version: + raise TypeError(f"{a} and {b} are not of the same version") + return (b.network_address <= a.network_address and + b.broadcast_address >= a.broadcast_address) + except AttributeError: + raise TypeError( + f"Unable to test subnet containment between {a} and {b}: " + f"both must be network objects") from None def subnet_of(self, other): """Return True if this network is a subnet of other.""" diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 73e954a2276d679..ab2f15fd3b1f9c6 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -733,9 +733,8 @@ def test_subnet_of_non_network(self): with self.assertRaises(TypeError) as cm: method(other) self.assertIn('network', str(cm.exception)) - # The error should not be a swallowed AttributeError. - self.assertNotIsInstance(cm.exception.__context__, - AttributeError) + # The internal AttributeError is not shown to the caller. + self.assertTrue(cm.exception.__suppress_context__) class NetmaskTestMixin_v6(CommonTestMixin_v6): diff --git a/Misc/NEWS.d/next/Library/2026-07-19-16-30-00.gh-issue-153769.STygbq.rst b/Misc/NEWS.d/next/Library/2026-07-19-16-30-00.gh-issue-153769.STygbq.rst index 587ae0c912b98d8..039b4c1df99a079 100644 --- a/Misc/NEWS.d/next/Library/2026-07-19-16-30-00.gh-issue-153769.STygbq.rst +++ b/Misc/NEWS.d/next/Library/2026-07-19-16-30-00.gh-issue-153769.STygbq.rst @@ -1,5 +1,4 @@ :meth:`~ipaddress.IPv4Network.subnet_of` and -:meth:`~ipaddress.IPv4Network.supernet_of` now raise a clear -:exc:`TypeError` when passed an argument that is not a network object, -such as an address, instead of a confusing error that masked an internal -:exc:`AttributeError`. +:meth:`~ipaddress.IPv4Network.supernet_of` now say that both arguments must be +network objects when passed something else, such as an address, and no longer +show the internal :exc:`AttributeError` behind it.