From a6b09c3a7331dd9f76753fc0684379adb01d5e7c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 29 Mar 2024 15:22:59 -1000 Subject: [PATCH 01/40] Fix AsyncResolver to match ThreadedResolver behavior AsyncResolver was disabled by default because it did not implement all of functionality of ThreadedResolver because aiodns did not support getaddrinfo until https://github.com/saghul/aiodns/pull/118 see https://github.com/aio-libs/aiohttp/issues/559 --- aiohttp/resolver.py | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index fe83ad27fd0..b9d77b122b1 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -1,6 +1,6 @@ import asyncio import socket -from typing import Any, Dict, List, Type, Union +from typing import Any, Dict, List, Tuple, Type, Union from .abc import AbstractResolver @@ -85,19 +85,49 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self._resolver = aiodns.DNSResolver(*args, loop=self._loop, **kwargs) async def resolve( - self, host: str, port: int = 0, family: int = socket.AF_INET + self, hostname: str, port: int = 0, family: int = socket.AF_INET ) -> List[Dict[str, Any]]: try: - resp = await self._resolver.gethostbyname(host, family) + resp = await self._resolver.getaddrinfo( + hostname, + port=port, + type=socket.SOCK_STREAM, + family=family, + flags=socket.AI_ADDRCONFIG, + ) except aiodns.error.DNSError as exc: msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" raise OSError(msg) from exc hosts = [] - for address in resp.addresses: + for node in resp.nodes: + address: Union[Tuple[bytes, int], Tuple[bytes, int, int, int]] = node.addr + family = node.family + if family == socket.AF_INET6: + if len(address) < 3: + # IPv6 is not supported by Python build, + # or IPv6 is not enabled in the host + continue + if address[3]: + # This is essential for link-local IPv6 addresses. + # LL IPv6 is a VERY rare case. Strictly speaking, we should use + # getnameinfo() unconditionally, but performance makes sense. + host, _port = await self._loop.getnameinfo( + address[0].decode("ascii"), + *address[1:], + socket.NI_NUMERICHOST | socket.NI_NUMERICSERV + ) + port = int(_port) + else: + host = address[0].decode("ascii") + port = address[1] + else: # IPv4 + assert family == socket.AF_INET + host = address[0].decode("ascii") + port = address[1] hosts.append( { - "hostname": host, - "host": address, + "hostname": hostname, + "host": host, "port": port, "family": family, "proto": 0, From d512342eb7e35988500c58722fd1b53523f14d7f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 07:52:31 -1000 Subject: [PATCH 02/40] fixes --- aiohttp/resolver.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index b9d77b122b1..af0fff14a76 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -27,10 +27,10 @@ def __init__(self) -> None: self._loop = asyncio.get_running_loop() async def resolve( - self, hostname: str, port: int = 0, family: int = socket.AF_INET + self, host: str, port: int = 0, family: int = socket.AF_INET ) -> List[Dict[str, Any]]: infos = await self._loop.getaddrinfo( - hostname, + host, port, type=socket.SOCK_STREAM, family=family, @@ -48,19 +48,19 @@ async def resolve( # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. - host, _port = socket.getnameinfo( + resolved_host, _port = socket.getnameinfo( address, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV ) port = int(_port) else: - host, port = address[:2] + resolved_host, port = address[:2] else: # IPv4 assert family == socket.AF_INET - host, port = address # type: ignore[misc] + resolved_host, port = address # type: ignore[misc] hosts.append( { - "hostname": hostname, - "host": host, + "hostname": host, + "host": resolved_host, "port": port, "family": family, "proto": proto, @@ -85,11 +85,11 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self._resolver = aiodns.DNSResolver(*args, loop=self._loop, **kwargs) async def resolve( - self, hostname: str, port: int = 0, family: int = socket.AF_INET + self, host: str, port: int = 0, family: int = socket.AF_INET ) -> List[Dict[str, Any]]: try: resp = await self._resolver.getaddrinfo( - hostname, + host, port=port, type=socket.SOCK_STREAM, family=family, @@ -111,23 +111,23 @@ async def resolve( # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. - host, _port = await self._loop.getnameinfo( + resolved_host, _port = await self._resolver.getnameinfo( address[0].decode("ascii"), *address[1:], socket.NI_NUMERICHOST | socket.NI_NUMERICSERV ) port = int(_port) else: - host = address[0].decode("ascii") + resolved_host = address[0].decode("ascii") port = address[1] else: # IPv4 assert family == socket.AF_INET - host = address[0].decode("ascii") + resolved_host = address[0].decode("ascii") port = address[1] hosts.append( { - "hostname": hostname, - "host": host, + "hostname": host, + "host": resolved_host, "port": port, "family": family, "proto": 0, From 13ff9cf7304b58d17f6856b492ff1c1a1cf7f934 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 11:21:50 -1000 Subject: [PATCH 03/40] fix tests --- tests/test_resolver.py | 50 ++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index c4cc31c1502..b93d0c96077 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -1,7 +1,7 @@ import asyncio import ipaddress import socket -from typing import Any, Awaitable, Callable, List +from typing import Any, Awaitable, Callable, Iterable, List, NamedTuple, Tuple, Union from unittest.mock import Mock, patch import pytest @@ -17,11 +17,19 @@ gethostbyname = False -class FakeResult: +class FakeAIODNSAddrInfoNode(NamedTuple): + + family: int + addr: Union[Tuple[bytes, int], Tuple[bytes, int, int, int]] + + +class FakeAIODNSAddrInfoResult: addresses: Any - def __init__(self, addresses: Any) -> None: - self.addresses = addresses + def __init__(self, hosts: Iterable[str]) -> None: + self.nodes = [ + FakeAIODNSAddrInfoNode(socket.AF_INET, [h.encode(), 0]) for h in hosts + ] class FakeQueryResult: @@ -31,15 +39,17 @@ def __init__(self, host: Any) -> None: self.host = host -async def fake_result(addresses: Any) -> FakeResult: - return FakeResult(addresses=tuple(addresses)) +async def fake_aiodns_getaddrinfo_result( + hosts: Iterable[str], +) -> FakeAIODNSAddrInfoResult: + return FakeAIODNSAddrInfoResult(hosts=hosts) async def fake_query_result(result: Any) -> List[FakeQueryResult]: return [FakeQueryResult(host=h) for h in result] -def fake_addrinfo(hosts: Any) -> Callable[..., Awaitable[Any]]: +def fake_addrinfo(hosts: Iterable[str]) -> Callable[..., Awaitable[Any]]: async def fake(*args: Any, **kwargs: Any) -> List[Any]: if not hosts: raise socket.gaierror @@ -52,18 +62,24 @@ async def fake(*args: Any, **kwargs: Any) -> List[Any]: @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") async def test_async_resolver_positive_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: - mock().gethostbyname.return_value = fake_result(["127.0.0.1"]) + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_result(["127.0.0.1"]) resolver = AsyncResolver() real = await resolver.resolve("www.python.org") ipaddress.ip_address(real[0]["host"]) - mock().gethostbyname.assert_called_with("www.python.org", socket.AF_INET) + mock().getaddrinfo.assert_called_with( + "www.python.org", + family=socket.AF_INET, + flags=socket.AI_ADDRCONFIG, + port=0, + type=socket.SOCK_STREAM, + ) @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") async def test_async_resolver_multiple_replies(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: ips = ["127.0.0.1", "127.0.0.2", "127.0.0.3", "127.0.0.4"] - mock().gethostbyname.return_value = fake_result(ips) + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_result(ips) resolver = AsyncResolver() real = await resolver.resolve("www.google.com") ipaddrs = [ipaddress.ip_address(x["host"]) for x in real] @@ -73,7 +89,7 @@ async def test_async_resolver_multiple_replies(loop: Any) -> None: @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") async def test_async_resolver_negative_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: - mock().gethostbyname.side_effect = aiodns.error.DNSError() + mock().getaddrinfo.side_effect = aiodns.error.DNSError() resolver = AsyncResolver() with pytest.raises(OSError): await resolver.resolve("doesnotexist.bla") @@ -82,7 +98,7 @@ async def test_async_resolver_negative_lookup(loop: Any) -> None: @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") async def test_async_resolver_no_hosts_in_gethostbyname(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: - mock().gethostbyname.return_value = fake_result([]) + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_result([]) resolver = AsyncResolver() with pytest.raises(OSError): await resolver.resolve("doesnotexist.bla") @@ -170,11 +186,17 @@ async def test_default_loop_for_async_resolver(loop: Any) -> None: @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") async def test_async_resolver_ipv6_positive_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: - mock().gethostbyname.return_value = fake_result(["::1"]) + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_result(["::1"]) resolver = AsyncResolver() real = await resolver.resolve("www.python.org", family=socket.AF_INET6) ipaddress.ip_address(real[0]["host"]) - mock().gethostbyname.assert_called_with("www.python.org", socket.AF_INET6) + mock().getaddrinfo.assert_called_with( + "www.python.org", + family=socket.AF_INET6, + flags=socket.AI_ADDRCONFIG, + port=0, + type=socket.SOCK_STREAM, + ) async def test_async_resolver_aiodns_not_present(loop: Any, monkeypatch: Any) -> None: From 28d3651fbb31f17ebc31c4b5f2b3511f874a04db Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 11:25:37 -1000 Subject: [PATCH 04/40] fix tests --- tests/test_resolver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index b93d0c96077..cd46c406d98 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -1,7 +1,7 @@ import asyncio import ipaddress import socket -from typing import Any, Awaitable, Callable, Iterable, List, NamedTuple, Tuple, Union +from typing import Any, Awaitable, Callable, Collection, List, NamedTuple, Tuple, Union from unittest.mock import Mock, patch import pytest @@ -26,7 +26,7 @@ class FakeAIODNSAddrInfoNode(NamedTuple): class FakeAIODNSAddrInfoResult: addresses: Any - def __init__(self, hosts: Iterable[str]) -> None: + def __init__(self, hosts: Collection[str]) -> None: self.nodes = [ FakeAIODNSAddrInfoNode(socket.AF_INET, [h.encode(), 0]) for h in hosts ] @@ -40,7 +40,7 @@ def __init__(self, host: Any) -> None: async def fake_aiodns_getaddrinfo_result( - hosts: Iterable[str], + hosts: Collection[str], ) -> FakeAIODNSAddrInfoResult: return FakeAIODNSAddrInfoResult(hosts=hosts) @@ -49,7 +49,7 @@ async def fake_query_result(result: Any) -> List[FakeQueryResult]: return [FakeQueryResult(host=h) for h in result] -def fake_addrinfo(hosts: Iterable[str]) -> Callable[..., Awaitable[Any]]: +def fake_addrinfo(hosts: Collection[str]) -> Callable[..., Awaitable[Any]]: async def fake(*args: Any, **kwargs: Any) -> List[Any]: if not hosts: raise socket.gaierror From b17c0873f411cd225a1cf48353165cf51f17444b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 11:27:03 -1000 Subject: [PATCH 05/40] use async api --- aiohttp/resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index af0fff14a76..797362f585c 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -48,7 +48,7 @@ async def resolve( # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. - resolved_host, _port = socket.getnameinfo( + resolved_host, _port = await self._loop.getnameinfo( address, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV ) port = int(_port) From 67e99e7b59694384b2b7d8027584a9b357c41737 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 11:34:00 -1000 Subject: [PATCH 06/40] avoid looped runtime construction of enums --- aiohttp/resolver.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index 797362f585c..df9f9a73891 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -15,6 +15,8 @@ aiodns_default = False +_NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV + class ThreadedResolver(AbstractResolver): """Threaded resolver. @@ -49,7 +51,7 @@ async def resolve( # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. resolved_host, _port = await self._loop.getnameinfo( - address, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV + address, _NUMERIC_SOCKET_FLAGS ) port = int(_port) else: @@ -64,7 +66,7 @@ async def resolve( "port": port, "family": family, "proto": proto, - "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV, + "flags": _NUMERIC_SOCKET_FLAGS, } ) @@ -112,9 +114,7 @@ async def resolve( # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. resolved_host, _port = await self._resolver.getnameinfo( - address[0].decode("ascii"), - *address[1:], - socket.NI_NUMERICHOST | socket.NI_NUMERICSERV + address[0].decode("ascii"), *address[1:], _NUMERIC_SOCKET_FLAGS ) port = int(_port) else: @@ -131,7 +131,7 @@ async def resolve( "port": port, "family": family, "proto": 0, - "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV, + "flags": _NUMERIC_SOCKET_FLAGS, } ) From 763f26e4712b75ecbe2b354bfc1e31eded97d678 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 11:36:09 -1000 Subject: [PATCH 07/40] typing --- aiohttp/abc.py | 13 ++++++++++++- aiohttp/resolver.py | 41 +++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/aiohttp/abc.py b/aiohttp/abc.py index 2d8036a3416..adefd3734ce 100644 --- a/aiohttp/abc.py +++ b/aiohttp/abc.py @@ -13,6 +13,7 @@ List, Optional, Tuple, + TypedDict, ) from multidict import CIMultiDict @@ -117,11 +118,21 @@ def __await__(self) -> Generator[Any, None, StreamResponse]: """Execute the view handler.""" +class ResolveResult(TypedDict): + + hostname: str + host: str + port: int + family: int + proto: int + flags: int + + class AbstractResolver(ABC): """Abstract DNS resolver.""" @abstractmethod - async def resolve(self, host: str, port: int, family: int) -> List[Dict[str, Any]]: + async def resolve(self, host: str, port: int, family: int) -> List[ResolveResult]: """Return IP address for given hostname""" @abstractmethod diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index df9f9a73891..512ce694827 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -1,8 +1,8 @@ import asyncio import socket -from typing import Any, Dict, List, Tuple, Type, Union +from typing import Any, List, Tuple, Type, Union -from .abc import AbstractResolver +from .abc import AbstractResolver, ResolveResult __all__ = ("ThreadedResolver", "AsyncResolver", "DefaultResolver") @@ -13,6 +13,7 @@ except ImportError: # pragma: no cover aiodns = None + aiodns_default = False _NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV @@ -30,7 +31,7 @@ def __init__(self) -> None: async def resolve( self, host: str, port: int = 0, family: int = socket.AF_INET - ) -> List[Dict[str, Any]]: + ) -> List[ResolveResult]: infos = await self._loop.getaddrinfo( host, port, @@ -60,14 +61,14 @@ async def resolve( assert family == socket.AF_INET resolved_host, port = address # type: ignore[misc] hosts.append( - { - "hostname": host, - "host": resolved_host, - "port": port, - "family": family, - "proto": proto, - "flags": _NUMERIC_SOCKET_FLAGS, - } + ResolveResult( + hostname=host, + host=resolved_host, + port=port, + family=family, + proto=proto, + flags=_NUMERIC_SOCKET_FLAGS, + ) ) return hosts @@ -88,7 +89,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: async def resolve( self, host: str, port: int = 0, family: int = socket.AF_INET - ) -> List[Dict[str, Any]]: + ) -> List[ResolveResult]: try: resp = await self._resolver.getaddrinfo( host, @@ -125,14 +126,14 @@ async def resolve( resolved_host = address[0].decode("ascii") port = address[1] hosts.append( - { - "hostname": host, - "host": resolved_host, - "port": port, - "family": family, - "proto": 0, - "flags": _NUMERIC_SOCKET_FLAGS, - } + ResolveResult( + hostname=host, + host=resolved_host, + port=port, + family=family, + proto=0, + flags=_NUMERIC_SOCKET_FLAGS, + ) ) if not hosts: From 1c222029cca60dc2059acc7bed81d38eff6ff9b9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 11:41:34 -1000 Subject: [PATCH 08/40] match actual signature --- aiohttp/abc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/aiohttp/abc.py b/aiohttp/abc.py index adefd3734ce..ca2abd4be3d 100644 --- a/aiohttp/abc.py +++ b/aiohttp/abc.py @@ -1,4 +1,5 @@ import logging +import socket from abc import ABC, abstractmethod from collections.abc import Sized from http.cookies import BaseCookie, Morsel @@ -132,7 +133,9 @@ class AbstractResolver(ABC): """Abstract DNS resolver.""" @abstractmethod - async def resolve(self, host: str, port: int, family: int) -> List[ResolveResult]: + async def resolve( + self, host: str, port: int = 0, family: int = socket.AF_INET + ) -> List[ResolveResult]: """Return IP address for given hostname""" @abstractmethod From 8fbc80dfb4aa1dbdd36ec35ef4c5bfda3b71ebc3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 12:02:54 -1000 Subject: [PATCH 09/40] match actual signature --- aiohttp/connector.py | 4 ++-- examples/fake_server.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 025c4a3a252..2d00933f260 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -35,7 +35,7 @@ import aiohappyeyeballs from . import hdrs, helpers -from .abc import AbstractResolver +from .abc import AbstractResolver, ResolveResult from .client_exceptions import ( ClientConnectionError, ClientConnectorCertificateError, @@ -813,7 +813,7 @@ def clear_dns_cache( async def _resolve_host( self, host: str, port: int, traces: Optional[List["Trace"]] = None - ) -> List[Dict[str, Any]]: + ) -> List[ResolveResult]: """Resolve host and return list of addresses.""" if is_ip_address(host): return [ diff --git a/examples/fake_server.py b/examples/fake_server.py index b3ca228d291..9feb0a9eea2 100755 --- a/examples/fake_server.py +++ b/examples/fake_server.py @@ -3,10 +3,10 @@ import pathlib import socket import ssl -from typing import Any, Dict, List, Union +from typing import Dict, List, Union from aiohttp import ClientSession, TCPConnector, resolver, test_utils, web -from aiohttp.abc import AbstractResolver +from aiohttp.abc import AbstractResolver, ResolveResult class FakeResolver(AbstractResolver): @@ -22,7 +22,7 @@ async def resolve( host: str, port: int = 0, family: Union[socket.AddressFamily, int] = socket.AF_INET, - ) -> List[Dict[str, Any]]: + ) -> List[ResolveResult]: fake_port = self._fakes.get(host) if fake_port is not None: return [ From 692b599e63dd605ff0b812504a946c6c9a38dc98 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 12:06:39 -1000 Subject: [PATCH 10/40] match actual signature --- aiohttp/connector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 2d00933f260..071f85dcb6b 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -881,7 +881,7 @@ async def _resolve_host_with_throttle( host: str, port: int, traces: Optional[List["Trace"]], - ) -> List[Dict[str, Any]]: + ) -> List[ResolveResult]: """Resolve host with a dns events throttle.""" if key in self._throttle_dns_events: # get event early, before any await (#4014) @@ -1129,7 +1129,7 @@ async def _start_tls_connection( return tls_transport, tls_proto def _convert_hosts_to_addr_infos( - self, hosts: List[Dict[str, Any]] + self, hosts: List[ResolveResult] ) -> List[aiohappyeyeballs.AddrInfoType]: """Converts the list of hosts to a list of addr_infos. From 88bd82c4ed3b38719c4ea62bf617e2f64b6fbe8d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 12:29:56 -1000 Subject: [PATCH 11/40] match actual signature --- aiohttp/connector.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 071f85dcb6b..baf1f8cfcab 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -674,14 +674,14 @@ async def _create_connection( class _DNSCacheTable: def __init__(self, ttl: Optional[float] = None) -> None: - self._addrs_rr: Dict[Tuple[str, int], Tuple[Iterator[Dict[str, Any]], int]] = {} + self._addrs_rr: Dict[Tuple[str, int], Tuple[Iterator[ResolveResult], int]] = {} self._timestamps: Dict[Tuple[str, int], float] = {} self._ttl = ttl def __contains__(self, host: object) -> bool: return host in self._addrs_rr - def add(self, key: Tuple[str, int], addrs: List[Dict[str, Any]]) -> None: + def add(self, key: Tuple[str, int], addrs: List[ResolveResult]) -> None: self._addrs_rr[key] = (cycle(addrs), len(addrs)) if self._ttl is not None: @@ -697,7 +697,7 @@ def clear(self) -> None: self._addrs_rr.clear() self._timestamps.clear() - def next_addrs(self, key: Tuple[str, int]) -> List[Dict[str, Any]]: + def next_addrs(self, key: Tuple[str, int]) -> List[ResolveResult]: loop, length = self._addrs_rr[key] addrs = list(islice(loop, length)) # Consume one more element to shift internal state of `cycle` @@ -868,7 +868,7 @@ async def _resolve_host( return await asyncio.shield(resolved_host_task) except asyncio.CancelledError: - def drop_exception(fut: "asyncio.Future[List[Dict[str, Any]]]") -> None: + def drop_exception(fut: "asyncio.Future[List[ResolveResult]]") -> None: with suppress(Exception, asyncio.CancelledError): fut.result() From 8aec2472e44bdd6342e362a5a6e2e7fde8d4c3f5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 12:32:30 -1000 Subject: [PATCH 12/40] match actual signature --- tests/test_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index cd46c406d98..84a79d34e75 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -28,7 +28,7 @@ class FakeAIODNSAddrInfoResult: def __init__(self, hosts: Collection[str]) -> None: self.nodes = [ - FakeAIODNSAddrInfoNode(socket.AF_INET, [h.encode(), 0]) for h in hosts + FakeAIODNSAddrInfoNode(socket.AF_INET, (h.encode(), 0)) for h in hosts ] From f90cbabd39fc8268ba4720b3c85593b6b8a7eace Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 12:44:30 -1000 Subject: [PATCH 13/40] typing --- aiohttp/resolver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index 512ce694827..92d8c818b64 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -40,7 +40,7 @@ async def resolve( flags=socket.AI_ADDRCONFIG, ) - hosts = [] + hosts: List[ResolveResult] = [] for family, _, proto, _, address in infos: if family == socket.AF_INET6: if len(address) < 3: @@ -101,7 +101,7 @@ async def resolve( except aiodns.error.DNSError as exc: msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" raise OSError(msg) from exc - hosts = [] + hosts: List[ResolveResult] = [] for node in resp.nodes: address: Union[Tuple[bytes, int], Tuple[bytes, int, int, int]] = node.addr family = node.family From 19d9019447ddf2f89c0ddd4c11b7eb1a6d272fcc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 13:08:14 -1000 Subject: [PATCH 14/40] link local --- aiohttp/resolver.py | 4 +- tests/test_resolver.py | 112 +++++++++++++++++++++++++++++++---------- 2 files changed, 87 insertions(+), 29 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index 92d8c818b64..4c86e1daf2d 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -114,10 +114,10 @@ async def resolve( # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. - resolved_host, _port = await self._resolver.getnameinfo( + result = await self._resolver.getnameinfo( address[0].decode("ascii"), *address[1:], _NUMERIC_SOCKET_FLAGS ) - port = int(_port) + resolved_host = result.node else: resolved_host = address[0].decode("ascii") port = address[1] diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 84a79d34e75..2f5c323db5e 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -1,12 +1,18 @@ import asyncio import ipaddress import socket +from ipaddress import ip_address from typing import Any, Awaitable, Callable, Collection, List, NamedTuple, Tuple, Union from unittest.mock import Mock, patch import pytest -from aiohttp.resolver import AsyncResolver, DefaultResolver, ThreadedResolver +from aiohttp.resolver import ( + _NUMERIC_SOCKET_FLAGS, + AsyncResolver, + DefaultResolver, + ThreadedResolver, +) try: import aiodns @@ -23,8 +29,7 @@ class FakeAIODNSAddrInfoNode(NamedTuple): addr: Union[Tuple[bytes, int], Tuple[bytes, int, int, int]] -class FakeAIODNSAddrInfoResult: - addresses: Any +class FakeAIODNSAddrInfoIPv4Result: def __init__(self, hosts: Collection[str]) -> None: self.nodes = [ @@ -32,6 +37,25 @@ def __init__(self, hosts: Collection[str]) -> None: ] +class FakeAIODNSAddrInfoIPv6Result: + + def __init__(self, hosts: Collection[str]) -> None: + self.nodes = [ + FakeAIODNSAddrInfoNode( + socket.AF_INET6, + (h.encode(), 0, 0, 3 if ip_address(h).is_link_local else 0), + ) + for h in hosts + ] + + +class FakeAIODNSNameInfoIPv6Result: + + def __init__(self, host: str) -> None: + self.node = host + self.service = None + + class FakeQueryResult: host: Any @@ -39,10 +63,20 @@ def __init__(self, host: Any) -> None: self.host = host -async def fake_aiodns_getaddrinfo_result( +async def fake_aiodns_getaddrinfo_ipv4_result( hosts: Collection[str], -) -> FakeAIODNSAddrInfoResult: - return FakeAIODNSAddrInfoResult(hosts=hosts) +) -> FakeAIODNSAddrInfoIPv4Result: + return FakeAIODNSAddrInfoIPv4Result(hosts=hosts) + + +async def fake_aiodns_getaddrinfo_ipv6_result( + hosts: Collection[str], +) -> FakeAIODNSAddrInfoIPv6Result: + return FakeAIODNSAddrInfoIPv6Result(hosts=hosts) + + +async def fake_aiodns_getnameinfo_ipv6_result(host: str) -> List[str]: + return FakeAIODNSNameInfoIPv6Result(host) async def fake_query_result(result: Any) -> List[FakeQueryResult]: @@ -60,9 +94,11 @@ async def fake(*args: Any, **kwargs: Any) -> List[Any]: @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") -async def test_async_resolver_positive_lookup(loop: Any) -> None: +async def test_async_resolver_positive_ipv4_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: - mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_result(["127.0.0.1"]) + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv4_result( + ["127.0.0.1"] + ) resolver = AsyncResolver() real = await resolver.resolve("www.python.org") ipaddress.ip_address(real[0]["host"]) @@ -75,11 +111,49 @@ async def test_async_resolver_positive_lookup(loop: Any) -> None: ) +@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +async def test_async_resolver_positive_ipv6_lookup(loop: Any) -> None: + with patch("aiodns.DNSResolver") as mock: + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv6_result(["::1"]) + resolver = AsyncResolver() + real = await resolver.resolve("www.python.org") + ipaddress.ip_address(real[0]["host"]) + mock().getaddrinfo.assert_called_with( + "www.python.org", + family=socket.AF_INET, + flags=socket.AI_ADDRCONFIG, + port=0, + type=socket.SOCK_STREAM, + ) + + +@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +async def test_async_resolver_positive_link_local_ipv6_lookup(loop: Any) -> None: + with patch("aiodns.DNSResolver") as mock: + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv6_result( + ["fe80::1"] + ) + mock().getnameinfo.return_value = fake_aiodns_getnameinfo_ipv6_result( + "fe80::1%eth0" + ) + resolver = AsyncResolver() + real = await resolver.resolve("www.python.org") + ipaddress.ip_address(real[0]["host"]) + mock().getaddrinfo.assert_called_with( + "www.python.org", + family=socket.AF_INET, + flags=socket.AI_ADDRCONFIG, + port=0, + type=socket.SOCK_STREAM, + ) + mock().getnameinfo.assert_called_with("fe80::1", 0, 0, 3, _NUMERIC_SOCKET_FLAGS) + + @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") async def test_async_resolver_multiple_replies(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: ips = ["127.0.0.1", "127.0.0.2", "127.0.0.3", "127.0.0.4"] - mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_result(ips) + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv4_result(ips) resolver = AsyncResolver() real = await resolver.resolve("www.google.com") ipaddrs = [ipaddress.ip_address(x["host"]) for x in real] @@ -96,9 +170,9 @@ async def test_async_resolver_negative_lookup(loop: Any) -> None: @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") -async def test_async_resolver_no_hosts_in_gethostbyname(loop: Any) -> None: +async def test_async_resolver_no_hosts_in_getaddrinfo(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: - mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_result([]) + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv4_result([]) resolver = AsyncResolver() with pytest.raises(OSError): await resolver.resolve("doesnotexist.bla") @@ -183,22 +257,6 @@ async def test_default_loop_for_async_resolver(loop: Any) -> None: assert resolver._loop is loop -@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") -async def test_async_resolver_ipv6_positive_lookup(loop: Any) -> None: - with patch("aiodns.DNSResolver") as mock: - mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_result(["::1"]) - resolver = AsyncResolver() - real = await resolver.resolve("www.python.org", family=socket.AF_INET6) - ipaddress.ip_address(real[0]["host"]) - mock().getaddrinfo.assert_called_with( - "www.python.org", - family=socket.AF_INET6, - flags=socket.AI_ADDRCONFIG, - port=0, - type=socket.SOCK_STREAM, - ) - - async def test_async_resolver_aiodns_not_present(loop: Any, monkeypatch: Any) -> None: monkeypatch.setattr("aiohttp.resolver.aiodns", None) with pytest.raises(RuntimeError): From 23824d94c9b312bcdf4fed9e7ac0941a845abce4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 13:11:05 -1000 Subject: [PATCH 15/40] link local threaded --- tests/test_resolver.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 2f5c323db5e..70ce5ae0a44 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -93,6 +93,32 @@ async def fake(*args: Any, **kwargs: Any) -> List[Any]: return fake +def fake_ipv6_addrinfo(hosts: Collection[str]) -> Callable[..., Awaitable[Any]]: + async def fake(*args: Any, **kwargs: Any) -> List[Any]: + if not hosts: + raise socket.gaierror + + return [ + ( + socket.AF_INET6, + None, + socket.SOCK_STREAM, + None, + (h, 0, 0, 3 if ip_address(h).is_link_local else 0), + ) + for h in hosts + ] + + return fake + + +def fake_ipv6_nameinfo(host: str) -> Callable[..., Awaitable[Any]]: + async def fake(*args: Any, **kwargs: Any) -> List[Any]: + return host, 0 + + return fake + + @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") async def test_async_resolver_positive_ipv4_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: @@ -188,6 +214,17 @@ async def test_threaded_resolver_positive_lookup() -> None: ipaddress.ip_address(real[0]["host"]) +async def test_threaded_resolver_positive_ipv6_link_local_lookup() -> None: + loop = Mock() + loop.getaddrinfo = fake_ipv6_addrinfo(["fe80::1"]) + loop.getnameinfo = fake_ipv6_nameinfo("fe80::1%eth0") + resolver = ThreadedResolver() + resolver._loop = loop + real = await resolver.resolve("www.python.org") + assert real[0]["hostname"] == "www.python.org" + ipaddress.ip_address(real[0]["host"]) + + async def test_threaded_resolver_multiple_replies() -> None: loop = Mock() ips = ["127.0.0.1", "127.0.0.2", "127.0.0.3", "127.0.0.4"] From 7c4d0b146bb4ba987602633b7af5d2f4d5c19bfa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 13:15:22 -1000 Subject: [PATCH 16/40] preen --- tests/test_resolver.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 70ce5ae0a44..32f030a819e 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -137,22 +137,6 @@ async def test_async_resolver_positive_ipv4_lookup(loop: Any) -> None: ) -@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") -async def test_async_resolver_positive_ipv6_lookup(loop: Any) -> None: - with patch("aiodns.DNSResolver") as mock: - mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv6_result(["::1"]) - resolver = AsyncResolver() - real = await resolver.resolve("www.python.org") - ipaddress.ip_address(real[0]["host"]) - mock().getaddrinfo.assert_called_with( - "www.python.org", - family=socket.AF_INET, - flags=socket.AI_ADDRCONFIG, - port=0, - type=socket.SOCK_STREAM, - ) - - @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") async def test_async_resolver_positive_link_local_ipv6_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: @@ -294,6 +278,22 @@ async def test_default_loop_for_async_resolver(loop: Any) -> None: assert resolver._loop is loop +@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +async def test_async_resolver_ipv6_positive_lookup(loop: Any) -> None: + with patch("aiodns.DNSResolver") as mock: + mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv6_result(["::1"]) + resolver = AsyncResolver() + real = await resolver.resolve("www.python.org") + ipaddress.ip_address(real[0]["host"]) + mock().getaddrinfo.assert_called_with( + "www.python.org", + family=socket.AF_INET, + flags=socket.AI_ADDRCONFIG, + port=0, + type=socket.SOCK_STREAM, + ) + + async def test_async_resolver_aiodns_not_present(loop: Any, monkeypatch: Any) -> None: monkeypatch.setattr("aiohttp.resolver.aiodns", None) with pytest.raises(RuntimeError): From c05fea7d29f26ac7b2500928a5106918f1ca157d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 13:27:17 -1000 Subject: [PATCH 17/40] typing --- tests/test_resolver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 32f030a819e..81cef1c0d57 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -75,7 +75,9 @@ async def fake_aiodns_getaddrinfo_ipv6_result( return FakeAIODNSAddrInfoIPv6Result(hosts=hosts) -async def fake_aiodns_getnameinfo_ipv6_result(host: str) -> List[str]: +async def fake_aiodns_getnameinfo_ipv6_result( + host: str, +) -> FakeAIODNSNameInfoIPv6Result: return FakeAIODNSNameInfoIPv6Result(host) From 4c858a67555799d930901bff10563f1312322839 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 13:31:33 -1000 Subject: [PATCH 18/40] typing --- tests/test_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 81cef1c0d57..00d191c63a5 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -115,7 +115,7 @@ async def fake(*args: Any, **kwargs: Any) -> List[Any]: def fake_ipv6_nameinfo(host: str) -> Callable[..., Awaitable[Any]]: - async def fake(*args: Any, **kwargs: Any) -> List[Any]: + async def fake(*args: Any, **kwargs: Any) -> Tuple[str, int]: return host, 0 return fake From 71d96b35048ccf94a59e22553ae0ca5d1bb447de Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 14:06:22 -1000 Subject: [PATCH 19/40] >=3.9.0 required for scope_id --- aiohttp/resolver.py | 6 ++++-- tests/test_resolver.py | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index 4c86e1daf2d..320607373d8 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -1,5 +1,6 @@ import asyncio import socket +import sys from typing import Any, List, Tuple, Type, Union from .abc import AbstractResolver, ResolveResult @@ -17,6 +18,7 @@ aiodns_default = False _NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV +_SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0) class ThreadedResolver(AbstractResolver): @@ -47,7 +49,7 @@ async def resolve( # IPv6 is not supported by Python build, # or IPv6 is not enabled in the host continue - if address[3]: + if address[3] and _SUPPORTS_SCOPE_ID: # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. @@ -110,7 +112,7 @@ async def resolve( # IPv6 is not supported by Python build, # or IPv6 is not enabled in the host continue - if address[3]: + if address[3] and _SUPPORTS_SCOPE_ID: # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 00d191c63a5..cde0eef5587 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -9,6 +9,7 @@ from aiohttp.resolver import ( _NUMERIC_SOCKET_FLAGS, + _SUPPORTS_SCOPE_ID, AsyncResolver, DefaultResolver, ThreadedResolver, @@ -140,6 +141,9 @@ async def test_async_resolver_positive_ipv4_lookup(loop: Any) -> None: @pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +@pytest.mark.skipif( + not _SUPPORTS_SCOPE_ID, reason="python version does not support scope id" +) async def test_async_resolver_positive_link_local_ipv6_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv6_result( @@ -200,6 +204,9 @@ async def test_threaded_resolver_positive_lookup() -> None: ipaddress.ip_address(real[0]["host"]) +@pytest.mark.skipif( + not _SUPPORTS_SCOPE_ID, reason="python version does not support scope id" +) async def test_threaded_resolver_positive_ipv6_link_local_lookup() -> None: loop = Mock() loop.getaddrinfo = fake_ipv6_addrinfo(["fe80::1"]) From ea7cb59eea7307832356feae6f8a340c60c9c317 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 14:29:45 -1000 Subject: [PATCH 20/40] remove unreachable code --- aiohttp/resolver.py | 4 ---- tests/test_resolver.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index 320607373d8..ef8805cbe79 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -45,10 +45,6 @@ async def resolve( hosts: List[ResolveResult] = [] for family, _, proto, _, address in infos: if family == socket.AF_INET6: - if len(address) < 3: - # IPv6 is not supported by Python build, - # or IPv6 is not enabled in the host - continue if address[3] and _SUPPORTS_SCOPE_ID: # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use diff --git a/tests/test_resolver.py b/tests/test_resolver.py index cde0eef5587..5f5f4c2cd3a 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -239,6 +239,16 @@ async def test_threaded_negative_lookup() -> None: await resolver.resolve("doesnotexist.bla") +async def test_threaded_negative_ipv6_lookup() -> None: + loop = Mock() + ips: List[Any] = [] + loop.getaddrinfo = fake_ipv6_addrinfo(ips) + resolver = ThreadedResolver() + resolver._loop = loop + with pytest.raises(socket.gaierror): + await resolver.resolve("doesnotexist.bla") + + async def test_threaded_negative_lookup_with_unknown_result() -> None: loop = Mock() From 68cddb1b56c889570344df0c64df579b7ef56d05 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 14:30:30 -1000 Subject: [PATCH 21/40] remove unreachable code --- aiohttp/resolver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index ef8805cbe79..32f0d016b57 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -45,6 +45,10 @@ async def resolve( hosts: List[ResolveResult] = [] for family, _, proto, _, address in infos: if family == socket.AF_INET6: + if len(address) < 3: + # IPv6 is not supported by Python build, + # or IPv6 is not enabled in the host + continue if address[3] and _SUPPORTS_SCOPE_ID: # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use @@ -104,10 +108,6 @@ async def resolve( address: Union[Tuple[bytes, int], Tuple[bytes, int, int, int]] = node.addr family = node.family if family == socket.AF_INET6: - if len(address) < 3: - # IPv6 is not supported by Python build, - # or IPv6 is not enabled in the host - continue if address[3] and _SUPPORTS_SCOPE_ID: # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use From 97a3ea939f6ec7b3be71294fee9da12e2e311eea Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 14:33:15 -1000 Subject: [PATCH 22/40] remove unreachable code --- aiohttp/resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index 32f0d016b57..5aefb8126a6 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -108,7 +108,7 @@ async def resolve( address: Union[Tuple[bytes, int], Tuple[bytes, int, int, int]] = node.addr family = node.family if family == socket.AF_INET6: - if address[3] and _SUPPORTS_SCOPE_ID: + if len(address) > 3 and address[3] and _SUPPORTS_SCOPE_ID: # This is essential for link-local IPv6 addresses. # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. From 10ee0e12502ff32e2eb7feed15f0f14baf86e599 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 06:45:59 -1000 Subject: [PATCH 23/40] Bump aiodns to 3.2.0+ --- setup.cfg | 2 +- tests/test_resolver.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/setup.cfg b/setup.cfg index 3f878b0d963..421f91bae47 100644 --- a/setup.cfg +++ b/setup.cfg @@ -64,7 +64,7 @@ install_requires = [options.extras_require] speedups = # required c-ares (aiodns' backend) will not build on windows - aiodns >= 1.1; sys_platform=="linux" or sys_platform=="darwin" + aiodns >= 3.2.0; sys_platform=="linux" or sys_platform=="darwin" Brotli; platform_python_implementation == 'CPython' brotlicffi; platform_python_implementation != 'CPython' diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 5f5f4c2cd3a..664406159f8 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -18,10 +18,10 @@ try: import aiodns - gethostbyname: Any = hasattr(aiodns.DNSResolver, "gethostbyname") + getaddrinfo: Any = hasattr(aiodns.DNSResolver, "getaddrinfo") except ImportError: aiodns = None - gethostbyname = False + getaddrinfo = False class FakeAIODNSAddrInfoNode(NamedTuple): @@ -122,7 +122,7 @@ async def fake(*args: Any, **kwargs: Any) -> Tuple[str, int]: return fake -@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required") async def test_async_resolver_positive_ipv4_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv4_result( @@ -140,7 +140,7 @@ async def test_async_resolver_positive_ipv4_lookup(loop: Any) -> None: ) -@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required") @pytest.mark.skipif( not _SUPPORTS_SCOPE_ID, reason="python version does not support scope id" ) @@ -165,7 +165,7 @@ async def test_async_resolver_positive_link_local_ipv6_lookup(loop: Any) -> None mock().getnameinfo.assert_called_with("fe80::1", 0, 0, 3, _NUMERIC_SOCKET_FLAGS) -@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required") async def test_async_resolver_multiple_replies(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: ips = ["127.0.0.1", "127.0.0.2", "127.0.0.3", "127.0.0.4"] @@ -176,7 +176,7 @@ async def test_async_resolver_multiple_replies(loop: Any) -> None: assert len(ipaddrs) > 3, "Expecting multiple addresses" -@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required") async def test_async_resolver_negative_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: mock().getaddrinfo.side_effect = aiodns.error.DNSError() @@ -185,7 +185,7 @@ async def test_async_resolver_negative_lookup(loop: Any) -> None: await resolver.resolve("doesnotexist.bla") -@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required") async def test_async_resolver_no_hosts_in_getaddrinfo(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv4_result([]) @@ -297,7 +297,7 @@ async def test_default_loop_for_async_resolver(loop: Any) -> None: assert resolver._loop is loop -@pytest.mark.skipif(not gethostbyname, reason="aiodns 1.1 required") +@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required") async def test_async_resolver_ipv6_positive_lookup(loop: Any) -> None: with patch("aiodns.DNSResolver") as mock: mock().getaddrinfo.return_value = fake_aiodns_getaddrinfo_ipv6_result(["::1"]) From c8527a63ad7964de1d6f89473c1a23d686b98d6f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 06:49:15 -1000 Subject: [PATCH 24/40] changes --- CHANGES/8270.bugfix.rst | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 CHANGES/8270.bugfix.rst diff --git a/CHANGES/8270.bugfix.rst b/CHANGES/8270.bugfix.rst new file mode 100644 index 00000000000..db39df6123b --- /dev/null +++ b/CHANGES/8270.bugfix.rst @@ -0,0 +1,9 @@ +Fix AsyncResolver to match ThreadedResolver behavior +-- by :user:`bdraco`. + +On system with IPv6 support, the AsyncResolver would not fallback +to providing A records when AAAA records were not available. +Additionally, unlikely the ThreadedResolver, the AsyncResolver +did not handle link-local addresses correctly. + +This change makes the behavior consistent with the ThreadedResolver. From df90484068b702bcb92d4eb2263c8949df7d883f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 06:50:17 -1000 Subject: [PATCH 25/40] changes --- aiohttp/abc.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/aiohttp/abc.py b/aiohttp/abc.py index ca2abd4be3d..0131197e992 100644 --- a/aiohttp/abc.py +++ b/aiohttp/abc.py @@ -120,6 +120,18 @@ def __await__(self) -> Generator[Any, None, StreamResponse]: class ResolveResult(TypedDict): + """Resolve result. + + This is the result returned from an AbstractResolver's + resolve method. + + :param hostname: The hostname that was provided. + :param host: The IP address that was resolved. + :param port: The port that was resolved. + :param family: The address family that was resolved. + :param proto: The protocol that was resolved. + :param flags: The flags that were resolved. + """ hostname: str host: str From 420e17070ae045797da60630acd15868c0f77dee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 06:51:32 -1000 Subject: [PATCH 26/40] changes --- aiohttp/resolver.py | 2 +- tests/test_resolver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index 5aefb8126a6..68fd44f08b1 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -10,7 +10,7 @@ try: import aiodns - # aiodns_default = hasattr(aiodns.DNSResolver, 'gethostbyname') + # aiodns_default = hasattr(aiodns.DNSResolver, 'getaddrinfo') except ImportError: # pragma: no cover aiodns = None diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 664406159f8..a37364ad522 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -320,7 +320,7 @@ async def test_async_resolver_aiodns_not_present(loop: Any, monkeypatch: Any) -> def test_default_resolver() -> None: - # if gethostbyname: + # if getaddrinfo: # assert DefaultResolver is AsyncResolver # else: # assert DefaultResolver is ThreadedResolver From 8ed7095abdd37da82f7a1b5ba053302bf748ff0d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 06:52:00 -1000 Subject: [PATCH 27/40] Update CHANGES/8270.bugfix.rst --- CHANGES/8270.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/8270.bugfix.rst b/CHANGES/8270.bugfix.rst index db39df6123b..074bb7a7d60 100644 --- a/CHANGES/8270.bugfix.rst +++ b/CHANGES/8270.bugfix.rst @@ -3,7 +3,7 @@ Fix AsyncResolver to match ThreadedResolver behavior On system with IPv6 support, the AsyncResolver would not fallback to providing A records when AAAA records were not available. -Additionally, unlikely the ThreadedResolver, the AsyncResolver +Additionally, unlike the ThreadedResolver, the AsyncResolver did not handle link-local addresses correctly. This change makes the behavior consistent with the ThreadedResolver. From d2db4b0ec22c072c151ffb775c1cf76c7a901411 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 06:53:29 -1000 Subject: [PATCH 28/40] adjust --- requirements/runtime-deps.in | 2 +- requirements/runtime-deps.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/runtime-deps.in b/requirements/runtime-deps.in index 639b7c5b739..d1b53e95d53 100644 --- a/requirements/runtime-deps.in +++ b/requirements/runtime-deps.in @@ -1,6 +1,6 @@ # Extracted from `setup.cfg` via `make sync-direct-runtime-deps` -aiodns >= 1.1; sys_platform=="linux" or sys_platform=="darwin" +aiodns >= 2.3.0; sys_platform=="linux" or sys_platform=="darwin" aiohappyeyeballs >= 2.3.0 aiosignal >= 1.1.2 async-timeout >= 4.0, < 5.0 ; python_version < "3.11" diff --git a/requirements/runtime-deps.txt b/requirements/runtime-deps.txt index ae3561aa8d1..108285bdf64 100644 --- a/requirements/runtime-deps.txt +++ b/requirements/runtime-deps.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --output-file=requirements/runtime-deps.txt --strip-extras requirements/runtime-deps.in # -aiodns==3.1.1 ; sys_platform == "linux" or sys_platform == "darwin" +aiodns==3.2.0 ; sys_platform == "linux" or sys_platform == "darwin" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.3.2 # via -r requirements/runtime-deps.in From c61211c293ffd0c93de2edf2e43b7a7cfeb106ee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 06:55:24 -1000 Subject: [PATCH 29/40] fix typo --- requirements/runtime-deps.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/runtime-deps.in b/requirements/runtime-deps.in index d1b53e95d53..1ce6b5acef1 100644 --- a/requirements/runtime-deps.in +++ b/requirements/runtime-deps.in @@ -1,6 +1,6 @@ # Extracted from `setup.cfg` via `make sync-direct-runtime-deps` -aiodns >= 2.3.0; sys_platform=="linux" or sys_platform=="darwin" +aiodns >= 3.2.0; sys_platform=="linux" or sys_platform=="darwin" aiohappyeyeballs >= 2.3.0 aiosignal >= 1.1.2 async-timeout >= 4.0, < 5.0 ; python_version < "3.11" From dd7bd22d71fb9139f7afc5a3999fd0a446ea03dd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 06:57:38 -1000 Subject: [PATCH 30/40] missed some --- requirements/base.txt | 2 +- requirements/constraints.txt | 2 +- requirements/dev.txt | 2 +- requirements/test.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 57c6599b7f8..7f48c7c4f7b 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --output-file=requirements/base.txt --strip-extras requirements/base.in # -aiodns==3.1.1 ; sys_platform == "linux" or sys_platform == "darwin" +aiodns==3.2.0 ; sys_platform == "linux" or sys_platform == "darwin" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.3.2 # via -r requirements/runtime-deps.in diff --git a/requirements/constraints.txt b/requirements/constraints.txt index ab44df81eb5..511f8e9cad4 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --output-file=requirements/constraints.txt --resolver=backtracking --strip-extras requirements/constraints.in # -aiodns==3.1.1 ; sys_platform == "linux" or sys_platform == "darwin" +aiodns==3.2.0 ; sys_platform == "linux" or sys_platform == "darwin" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.3.2 # via -r requirements/runtime-deps.in diff --git a/requirements/dev.txt b/requirements/dev.txt index cd9363a36f7..01f4b62878a 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --output-file=requirements/dev.txt --resolver=backtracking --strip-extras requirements/dev.in # -aiodns==3.1.1 ; sys_platform == "linux" or sys_platform == "darwin" +aiodns==3.2.0 ; sys_platform == "linux" or sys_platform == "darwin" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.3.2 # via -r requirements/runtime-deps.in diff --git a/requirements/test.txt b/requirements/test.txt index 6130aab55d2..02171837485 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --output-file=requirements/test.txt --resolver=backtracking --strip-extras requirements/test.in # -aiodns==3.1.1 ; sys_platform == "linux" or sys_platform == "darwin" +aiodns==3.2.0 ; sys_platform == "linux" or sys_platform == "darwin" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.3.2 # via -r requirements/runtime-deps.in From 4f492651b3ae3e693cbbf33916fe05d73e2d046e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 07:00:36 -1000 Subject: [PATCH 31/40] tweak changes --- CHANGES/8270.bugfix.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES/8270.bugfix.rst b/CHANGES/8270.bugfix.rst index 074bb7a7d60..bda77223959 100644 --- a/CHANGES/8270.bugfix.rst +++ b/CHANGES/8270.bugfix.rst @@ -1,9 +1,9 @@ -Fix AsyncResolver to match ThreadedResolver behavior +Fix ``AsyncResolver`` to match ``ThreadedResolver`` behavior -- by :user:`bdraco`. -On system with IPv6 support, the AsyncResolver would not fallback +On system with IPv6 support, the :py:class:`~aiohttp.resolver.AsyncResolver` would not fallback to providing A records when AAAA records were not available. -Additionally, unlike the ThreadedResolver, the AsyncResolver +Additionally, unlike the :py:class:`~aiohttp.resolver.ThreadedResolver`, the :py:class:`~aiohttp.resolver.AsyncResolver` did not handle link-local addresses correctly. -This change makes the behavior consistent with the ThreadedResolver. +This change makes the behavior consistent with the :py:class:`~aiohttp.resolver.ThreadedResolver`. From 3d5175f2ddeec003d01793a4c7bd7319c7abc25d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 07:08:09 -1000 Subject: [PATCH 32/40] resolvers are currently not documented --- docs/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 5f3037b4bbc..b0fdc593f08 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -394,6 +394,8 @@ ("py:class", "aiohttp.ClientRequest"), # undocumented ("py:class", "aiohttp.payload.Payload"), # undocumented ("py:class", "aiohttp.abc.AbstractResolver"), # undocumented + ("py:class", "aiohttp.abc.AsyncResolver"), # undocumented + ("py:class", "aiohttp.abc.ThreadedResolver"), # undocumented ("py:func", "aiohttp.ws_connect"), # undocumented ("py:meth", "start"), # undocumented ("py:exc", "aiohttp.ClientHttpProxyError"), # undocumented From aa1f2e5a618e05c59ffe0f33706f85da8d8c3978 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 07:12:27 -1000 Subject: [PATCH 33/40] Update docs/conf.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b0fdc593f08..f7745ba3944 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -394,8 +394,8 @@ ("py:class", "aiohttp.ClientRequest"), # undocumented ("py:class", "aiohttp.payload.Payload"), # undocumented ("py:class", "aiohttp.abc.AbstractResolver"), # undocumented - ("py:class", "aiohttp.abc.AsyncResolver"), # undocumented - ("py:class", "aiohttp.abc.ThreadedResolver"), # undocumented + ("py:class", "aiohttp.resolver.AsyncResolver"), # undocumented + ("py:class", "aiohttp.resolver.ThreadedResolver"), # undocumented ("py:func", "aiohttp.ws_connect"), # undocumented ("py:meth", "start"), # undocumented ("py:exc", "aiohttp.ClientHttpProxyError"), # undocumented From 4347ba260e3854447c6d349f0c0d3bec2e32d96c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 07:27:22 -1000 Subject: [PATCH 34/40] fixes from manual testing - fix tuple construction --- aiohttp/resolver.py | 3 ++- tests/test_resolver.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/aiohttp/resolver.py b/aiohttp/resolver.py index 68fd44f08b1..ee9b4d0b9a5 100644 --- a/aiohttp/resolver.py +++ b/aiohttp/resolver.py @@ -113,7 +113,8 @@ async def resolve( # LL IPv6 is a VERY rare case. Strictly speaking, we should use # getnameinfo() unconditionally, but performance makes sense. result = await self._resolver.getnameinfo( - address[0].decode("ascii"), *address[1:], _NUMERIC_SOCKET_FLAGS + (address[0].decode("ascii"), *address[1:]), + _NUMERIC_SOCKET_FLAGS, ) resolved_host = result.node else: diff --git a/tests/test_resolver.py b/tests/test_resolver.py index a37364ad522..614f4beb89e 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -162,7 +162,9 @@ async def test_async_resolver_positive_link_local_ipv6_lookup(loop: Any) -> None port=0, type=socket.SOCK_STREAM, ) - mock().getnameinfo.assert_called_with("fe80::1", 0, 0, 3, _NUMERIC_SOCKET_FLAGS) + mock().getnameinfo.assert_called_with( + ("fe80::1", 0, 0, 3), _NUMERIC_SOCKET_FLAGS + ) @pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required") From 9ca6a9690fefd8e17daca227e2b97cc79c7e44df Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 07:42:22 -1000 Subject: [PATCH 35/40] add Abstract Resolver --- docs/abc.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/abc.rst b/docs/abc.rst index ee19a0915d1..4c836759677 100644 --- a/docs/abc.rst +++ b/docs/abc.rst @@ -181,3 +181,33 @@ Abstract Access Logger :param response: :class:`aiohttp.web.Response` object. :param float time: Time taken to serve the request. + + +Abstract Resolver +------------------------------- + +.. class:: AbstractResolver + + An abstract class, base for all resolver implementations + + Method ``resolve`` should be overridden. + + .. method:: resolve(host, port, family) + + Resolve host name to IP address. + + :param str host: hostname to resolve. + + :param int port: port number. + + :param int family: socket family. + + :return: list of :class:`aiohttp.abc.ResolveResult` instances. + + .. method:: close() + + Release resolver. + +.. autoclass:: ResolveResult + :members: + :undoc-members: From 9ea1eb45e3fb97d23b40a84f651f4cba93d134af Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 07:45:24 -1000 Subject: [PATCH 36/40] no autoclass --- docs/abc.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/abc.rst b/docs/abc.rst index 4c836759677..1c8f00a5d48 100644 --- a/docs/abc.rst +++ b/docs/abc.rst @@ -211,3 +211,31 @@ Abstract Resolver .. autoclass:: ResolveResult :members: :undoc-members: + +.. class:: ResolveResult + + Result of host name resolution. + + .. attribute:: hostname + + The hostname that was provided. + + .. attribute:: host + + The IP address that was resolved. + + .. attribute:: port + + The port that was resolved. + + .. attribute:: family + + The address family that was resolved. + + .. attribute:: proto + + The protocol that was resolved. + + .. attribute:: flags + + The flags that were resolved. From f9120e7ed4c3da1ba2d2b8a0a2f9e1f6571f2fc1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 07:47:47 -1000 Subject: [PATCH 37/40] no autoclass --- docs/abc.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/abc.rst b/docs/abc.rst index 1c8f00a5d48..b73d4819b54 100644 --- a/docs/abc.rst +++ b/docs/abc.rst @@ -208,10 +208,6 @@ Abstract Resolver Release resolver. -.. autoclass:: ResolveResult - :members: - :undoc-members: - .. class:: ResolveResult Result of host name resolution. From a1e592de61fd0cef9168f87436875bc22a308025 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 07:48:34 -1000 Subject: [PATCH 38/40] missing . --- docs/abc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/abc.rst b/docs/abc.rst index b73d4819b54..5d2242adcd7 100644 --- a/docs/abc.rst +++ b/docs/abc.rst @@ -188,7 +188,7 @@ Abstract Resolver .. class:: AbstractResolver - An abstract class, base for all resolver implementations + An abstract class, base for all resolver implementations. Method ``resolve`` should be overridden. From 4606536264b20779f895f53c03ffecab94ca78b8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 07:54:24 -1000 Subject: [PATCH 39/40] spelling --- docs/abc.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/abc.rst b/docs/abc.rst index 5d2242adcd7..d778a0a7bb3 100644 --- a/docs/abc.rst +++ b/docs/abc.rst @@ -196,7 +196,7 @@ Abstract Resolver Resolve host name to IP address. - :param str host: hostname to resolve. + :param str host: host name to resolve. :param int port: port number. @@ -214,7 +214,7 @@ Abstract Resolver .. attribute:: hostname - The hostname that was provided. + The host name that was provided. .. attribute:: host From 05160424f2ce8311afb0e1254e1a126650d75a84 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Mar 2024 13:52:55 -1000 Subject: [PATCH 40/40] Update conf.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- docs/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index f7745ba3944..cbf6afceb5c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -393,7 +393,6 @@ ("py:class", "aiohttp.protocol.HttpVersion"), # undocumented ("py:class", "aiohttp.ClientRequest"), # undocumented ("py:class", "aiohttp.payload.Payload"), # undocumented - ("py:class", "aiohttp.abc.AbstractResolver"), # undocumented ("py:class", "aiohttp.resolver.AsyncResolver"), # undocumented ("py:class", "aiohttp.resolver.ThreadedResolver"), # undocumented ("py:func", "aiohttp.ws_connect"), # undocumented