diff --git a/aiohttp/client.py b/aiohttp/client.py index 30a29c36c01..a6404bb91cc 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -77,8 +77,10 @@ SSL_ALLOWED_TYPES, ClientRequest, ClientResponse, + ClientTimeout, Fingerprint, RequestInfo, + ResponseParams, ) from .client_ws import ( DEFAULT_WS_CLIENT_TIMEOUT, @@ -98,7 +100,6 @@ EMPTY_BODY_METHODS, BasicAuth, TimeoutHandle, - frozen_dataclass_decorator, get_env_proxy_for_url, sentinel, strip_auth_from_url, @@ -197,28 +198,6 @@ class _RequestOptions(TypedDict, total=False): middlewares: Optional[Tuple[ClientMiddlewareType, ...]] -@frozen_dataclass_decorator -class ClientTimeout: - total: Optional[float] = None - connect: Optional[float] = None - sock_read: Optional[float] = None - sock_connect: Optional[float] = None - ceil_threshold: float = 5 - - # pool_queue_timeout: Optional[float] = None - # dns_resolution_timeout: Optional[float] = None - # socket_connect_timeout: Optional[float] = None - # connection_acquiring_timeout: Optional[float] = None - # new_connection_timeout: Optional[float] = None - # http_header_timeout: Optional[float] = None - # response_body_timeout: Optional[float] = None - - # to create a timeout specific for a single request, either - # - create a completely new one to overwrite the default - # - or use https://docs.python.org/3/library/dataclasses.html#dataclasses.replace - # to overwrite the defaults - - # 5 Minute default read timeout DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60, sock_connect=30) @@ -622,9 +601,23 @@ async def _request( get_env_proxy_for_url, url ) + response_params: ResponseParams = { + "timer": timer, + "skip_payload": method in EMPTY_BODY_METHODS, + "read_until_eof": read_until_eof, + "auto_decompress": auto_decompress, + "read_timeout": real_timeout.sock_read, + "read_bufsize": read_bufsize, + "timeout_ceil_threshold": self._connector._timeout_ceil_threshold, + "max_line_size": max_line_size, + "max_field_size": max_field_size, + } + req = self._request_class( method, url, + response_params=response_params, + timeout=real_timeout, params=params, headers=headers, skip_auto_headers=skip_headers, @@ -654,9 +647,10 @@ async def _connect_and_send_request( ) -> ClientResponse: # connection timeout assert self._connector is not None + assert req._timeout is not None try: conn = await self._connector.connect( - req, traces=traces, timeout=real_timeout + req, traces=traces, timeout=req._timeout ) except asyncio.TimeoutError as exc: raise ConnectionTimeoutError( @@ -664,17 +658,8 @@ async def _connect_and_send_request( ) from exc assert conn.protocol is not None - conn.protocol.set_response_params( - timer=timer, - skip_payload=req.method in EMPTY_BODY_METHODS, - read_until_eof=read_until_eof, - auto_decompress=auto_decompress, - read_timeout=real_timeout.sock_read, - read_bufsize=read_bufsize, - timeout_ceil_threshold=self._connector._timeout_ceil_threshold, - max_line_size=max_line_size, - max_field_size=max_field_size, - ) + assert req._response_params is not None + conn.protocol.set_response_params(**req._response_params) try: resp = await req.send(conn) try: diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index db4018efa1d..cc1c960492a 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -22,6 +22,7 @@ Optional, Tuple, Type, + TypedDict, Union, ) @@ -98,6 +99,28 @@ _CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") +@frozen_dataclass_decorator +class ClientTimeout: + total: Optional[float] = None + connect: Optional[float] = None + sock_read: Optional[float] = None + sock_connect: Optional[float] = None + ceil_threshold: float = 5 + + # pool_queue_timeout: Optional[float] = None + # dns_resolution_timeout: Optional[float] = None + # socket_connect_timeout: Optional[float] = None + # connection_acquiring_timeout: Optional[float] = None + # new_connection_timeout: Optional[float] = None + # http_header_timeout: Optional[float] = None + # response_body_timeout: Optional[float] = None + + # to create a timeout specific for a single request, either + # - create a completely new one to overwrite the default + # - or use https://docs.python.org/3/library/dataclasses.html#dataclasses.replace + # to overwrite the defaults + + def _gen_default_accept_encoding() -> str: return "gzip, deflate, br" if HAS_BROTLI else "gzip, deflate" @@ -190,6 +213,18 @@ class ConnectionKey(NamedTuple): proxy_headers_hash: Optional[int] # hash(CIMultiDict) +class ResponseParams(TypedDict): + timer: Optional[BaseTimerContext] + skip_payload: bool + read_until_eof: bool + auto_decompress: bool + read_timeout: Optional[float] + read_bufsize: int + timeout_ceil_threshold: float + max_line_size: int + max_field_size: int + + class ClientRequest: GET_METHODS = { hdrs.METH_GET, @@ -209,11 +244,13 @@ class ClientRequest: body: Any = b"" auth = None response = None + _response_params = None # These class defaults help create_autospec() work correctly. # If autospec is improved in future, maybe these can be removed. url = URL() method = "GET" + _timeout: Optional[ClientTimeout] = ClientTimeout() __writer: Optional["asyncio.Task[None]"] = None # async task for streaming data _continue = None # waiter future for '100 Continue' response @@ -251,6 +288,8 @@ def __init__( traces: Optional[List["Trace"]] = None, trust_env: bool = False, server_hostname: Optional[str] = None, + response_params: Optional[ResponseParams] = None, + timeout: Optional[ClientTimeout] = None, ): if match := _CONTAINS_CONTROL_CHAR_RE.search(method): raise ValueError( @@ -281,6 +320,8 @@ def __init__( self.response_class: Type[ClientResponse] = real_response_class self._timer = timer if timer is not None else TimerNoop() self._ssl = ssl + self._response_params = response_params + self._timeout = timeout self.server_hostname = server_hostname if loop.get_debug():