diff --git a/CHANGES/8455.feature.rst b/CHANGES/8455.feature.rst new file mode 100644 index 00000000000..267e5243afa --- /dev/null +++ b/CHANGES/8455.feature.rst @@ -0,0 +1 @@ +Added :exc:`aiohttp.ClientConnectorDNSError` for differentiating DNS resolution errors from other connector errors -- by :user:`mstojcevich`. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index c97977fe8ae..ac799f2498e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -230,6 +230,7 @@ Manuel Miranda Marat Sharafutdinov Marc Mueller Marco Paolini +Marcus Stojcevich Mariano Anaya Mariusz Masztalerczuk Marko Kohtala diff --git a/aiohttp/__init__.py b/aiohttp/__init__.py index af5243106c0..dc11fefc949 100644 --- a/aiohttp/__init__.py +++ b/aiohttp/__init__.py @@ -8,6 +8,7 @@ ClientConnectionError, ClientConnectionResetError, ClientConnectorCertificateError, + ClientConnectorDNSError, ClientConnectorError, ClientConnectorSSLError, ClientError, @@ -120,6 +121,7 @@ "ClientConnectionError", "ClientConnectionResetError", "ClientConnectorCertificateError", + "ClientConnectorDNSError", "ClientConnectorError", "ClientConnectorSSLError", "ClientError", diff --git a/aiohttp/client.py b/aiohttp/client.py index 8fce18b1a18..cb89ec839a9 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -44,6 +44,7 @@ ClientConnectionError, ClientConnectionResetError, ClientConnectorCertificateError, + ClientConnectorDNSError, ClientConnectorError, ClientConnectorSSLError, ClientError, @@ -110,6 +111,7 @@ "ClientConnectionError", "ClientConnectionResetError", "ClientConnectorCertificateError", + "ClientConnectorDNSError", "ClientConnectorError", "ClientConnectorSSLError", "ClientError", diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py index f9711bc2e71..b09c24d09e7 100644 --- a/aiohttp/client_exceptions.py +++ b/aiohttp/client_exceptions.py @@ -29,6 +29,7 @@ "ClientConnectorError", "ClientProxyConnectionError", "ClientSSLError", + "ClientConnectorDNSError", "ClientConnectorSSLError", "ClientConnectorCertificateError", "ConnectionTimeoutError", @@ -173,6 +174,14 @@ def __str__(self) -> str: __reduce__ = BaseException.__reduce__ +class ClientConnectorDNSError(ClientConnectorError): + """DNS resolution failed during client connection. + + Raised in :class:`aiohttp.connector.TCPConnector` if + DNS resolution fails. + """ + + class ClientProxyConnectionError(ClientConnectorError): """Proxy connection error. diff --git a/aiohttp/connector.py b/aiohttp/connector.py index d1a82e73315..e00ad196bad 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -39,6 +39,7 @@ from .client_exceptions import ( ClientConnectionError, ClientConnectorCertificateError, + ClientConnectorDNSError, ClientConnectorError, ClientConnectorSSLError, ClientHttpProxyError, @@ -1245,7 +1246,7 @@ async def _create_direct_connection( raise # in case of proxy it is not ClientProxyConnectionError # it is problem of resolving proxy ip itself - raise ClientConnectorError(req.connection_key, exc) from exc + raise ClientConnectorDNSError(req.connection_key, exc) from exc last_exc: Optional[Exception] = None addr_infos = self._convert_hosts_to_addr_infos(hosts) diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 347c7d5da60..8cb6c91c2bc 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -2254,6 +2254,12 @@ Connection errors Derived from :exc:`ClientOSError` +.. class:: ClientConnectorDNSError + + DNS resolution error. + + Derived from :exc:`ClientConnectorError` + .. class:: ClientProxyConnectionError Derived from :exc:`ClientConnectorError` @@ -2335,6 +2341,8 @@ Hierarchy of exceptions * :exc:`ClientProxyConnectionError` + * :exc:`ClientConnectorDNSError` + * :exc:`ClientSSLError` * :exc:`ClientConnectorCertificateError` diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index a0ae8ceaad9..14494029e32 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -3315,6 +3315,17 @@ async def test_aiohttp_request_ctx_manager_not_found() -> None: assert False, "never executed" # pragma: no cover +async def test_raising_client_connector_dns_error_on_dns_failure() -> None: + """Verify that the exception raised when a DNS lookup fails is specific to DNS.""" + with mock.patch( + "aiohttp.connector.TCPConnector._resolve_host", autospec=True, spec_set=True + ) as mock_resolve_host: + mock_resolve_host.side_effect = OSError(None, "DNS lookup failed") + with pytest.raises(aiohttp.ClientConnectorDNSError, match="DNS lookup failed"): + async with aiohttp.request("GET", "http://wrong-dns-name.com"): + assert False, "never executed" + + async def test_aiohttp_request_coroutine(aiohttp_server: AiohttpServer) -> None: async def handler(request: web.Request) -> web.Response: return web.Response()