Coverage for /pythoncovmergedfiles/medio/medio/src/aiohttp/aiohttp/client.py: 25%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""HTTP Client for asyncio."""
3import asyncio
4import base64
5import dataclasses
6import hashlib
7import json
8import os
9import sys
10import traceback
11import warnings
12from collections.abc import (
13 Awaitable,
14 Callable,
15 Collection,
16 Coroutine,
17 Generator,
18 Iterable,
19 Sequence,
20)
21from contextlib import suppress
22from types import TracebackType
23from typing import (
24 TYPE_CHECKING,
25 Any,
26 Generic,
27 Literal,
28 TypedDict,
29 TypeVar,
30 final,
31 overload,
32)
34from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr
35from yarl import URL, Query
37from . import hdrs, http, payload
38from ._websocket.reader import WebSocketDataQueue
39from .abc import AbstractCookieJar
40from .client_exceptions import (
41 ClientConnectionError,
42 ClientConnectionResetError,
43 ClientConnectorCertificateError,
44 ClientConnectorDNSError,
45 ClientConnectorError,
46 ClientConnectorSSLError,
47 ClientError,
48 ClientHttpProxyError,
49 ClientOSError,
50 ClientPayloadError,
51 ClientProxyConnectionError,
52 ClientResponseError,
53 ClientSSLError,
54 ConnectionTimeoutError,
55 ContentTypeError,
56 InvalidURL,
57 InvalidUrlClientError,
58 InvalidUrlRedirectClientError,
59 NonHttpUrlClientError,
60 NonHttpUrlRedirectClientError,
61 RedirectClientError,
62 ServerConnectionError,
63 ServerDisconnectedError,
64 ServerFingerprintMismatch,
65 ServerTimeoutError,
66 SocketTimeoutError,
67 TooManyRedirects,
68 WSMessageTypeError,
69 WSServerHandshakeError,
70)
71from .client_middlewares import ClientMiddlewareType, _cached_build_client_middlewares
72from .client_reqrep import (
73 SSL_ALLOWED_TYPES,
74 ClientRequest,
75 ClientResponse,
76 ClientTimeout,
77 Fingerprint,
78 RequestInfo,
79 ResponseParams,
80)
81from .client_ws import (
82 DEFAULT_WS_CLIENT_TIMEOUT,
83 ClientWebSocketResponse,
84 ClientWSTimeout,
85)
86from .connector import BaseConnector, NamedPipeConnector, TCPConnector, UnixConnector
87from .cookiejar import CookieJar
88from .helpers import (
89 _SENTINEL,
90 DEFAULT_CHUNK_SIZE,
91 EMPTY_BODY_METHODS,
92 HTTP_AND_EMPTY_SCHEMA_SET,
93 TimeoutHandle,
94 _auth_header_from_netrc,
95 get_env_proxy_for_url,
96 netrc_from_env,
97 sentinel,
98 strip_auth_from_url,
99)
100from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter
101from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse
102from .tracing import Trace, TraceConfig
103from .typedefs import (
104 JSONBytesEncoder,
105 JSONEncoder,
106 LooseCookies,
107 LooseHeaders,
108 StrOrURL,
109)
111__all__ = (
112 # client_exceptions
113 "ClientConnectionError",
114 "ClientConnectionResetError",
115 "ClientConnectorCertificateError",
116 "ClientConnectorDNSError",
117 "ClientConnectorError",
118 "ClientConnectorSSLError",
119 "ClientError",
120 "ClientHttpProxyError",
121 "ClientOSError",
122 "ClientPayloadError",
123 "ClientProxyConnectionError",
124 "ClientResponseError",
125 "ClientSSLError",
126 "ConnectionTimeoutError",
127 "ContentTypeError",
128 "InvalidURL",
129 "InvalidUrlClientError",
130 "RedirectClientError",
131 "NonHttpUrlClientError",
132 "InvalidUrlRedirectClientError",
133 "NonHttpUrlRedirectClientError",
134 "ServerConnectionError",
135 "ServerDisconnectedError",
136 "ServerFingerprintMismatch",
137 "ServerTimeoutError",
138 "SocketTimeoutError",
139 "TooManyRedirects",
140 "WSServerHandshakeError",
141 # client_reqrep
142 "ClientRequest",
143 "ClientResponse",
144 "Fingerprint",
145 "RequestInfo",
146 # connector
147 "BaseConnector",
148 "TCPConnector",
149 "UnixConnector",
150 "NamedPipeConnector",
151 # client_ws
152 "ClientWebSocketResponse",
153 # client
154 "ClientSession",
155 "ClientTimeout",
156 "ClientWSTimeout",
157 "request",
158 "WSMessageTypeError",
159)
162if TYPE_CHECKING:
163 from ssl import SSLContext
164else:
165 SSLContext = None
167if sys.version_info >= (3, 11) and TYPE_CHECKING:
168 from typing import Unpack
171class _RequestOptions(TypedDict, total=False):
172 params: Query
173 data: Any
174 json: Any
175 cookies: LooseCookies | None
176 headers: LooseHeaders | None
177 skip_auto_headers: Iterable[str] | None
178 allow_redirects: bool
179 max_redirects: int
180 compress: Literal["deflate", "gzip"] | bool
181 chunked: bool | None
182 expect100: bool
183 raise_for_status: None | bool | Callable[[ClientResponse], Awaitable[None]]
184 read_until_eof: bool
185 proxy: StrOrURL | None
186 timeout: "ClientTimeout | _SENTINEL | None"
187 ssl: SSLContext | bool | Fingerprint | _SENTINEL
188 server_hostname: str | None
189 proxy_headers: LooseHeaders | None
190 trace_request_ctx: object
191 read_bufsize: int | None
192 auto_decompress: bool | None
193 max_line_size: int | None
194 max_field_size: int | None
195 max_headers: int | None
196 middlewares: Sequence[ClientMiddlewareType] | None
199class _WSConnectOptions(TypedDict, total=False):
200 method: str
201 protocols: Collection[str]
202 timeout: "ClientWSTimeout | _SENTINEL"
203 receive_timeout: float | None
204 autoclose: bool
205 autoping: bool
206 heartbeat: float | None
207 origin: str | None
208 params: Query
209 headers: LooseHeaders | None
210 proxy: StrOrURL | None
211 ssl: SSLContext | bool | Fingerprint | _SENTINEL
212 server_hostname: str | None
213 proxy_headers: LooseHeaders | None
214 compress: int
215 max_msg_size: int
218# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2
219# https://www.rfc-editor.org/info/rfc10008/#section-1-12
220IDEMPOTENT_METHODS = frozenset(
221 {"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE", "QUERY"}
222)
224_RetType_co = TypeVar(
225 "_RetType_co",
226 bound="ClientResponse | ClientWebSocketResponse[bool]",
227 covariant=True,
228)
229_CharsetResolver = Callable[[ClientResponse, bytes], str]
232# Module-level (not a closure) so it has a stable identity for the
233# ``_cached_build_client_middlewares`` cache key.
234async def _connect_and_send_request(req: ClientRequest) -> ClientResponse:
235 connector = req._session._connector
236 assert connector is not None
237 try:
238 conn = await connector.connect(req, traces=req._traces, timeout=req._timeout)
239 except asyncio.TimeoutError as exc:
240 raise ConnectionTimeoutError(f"Connection timeout to host {req.url}") from exc
242 assert conn.protocol is not None
243 conn.protocol.set_response_params(**req._response_params)
244 try:
245 resp = await req._send(conn)
246 try:
247 await resp.start(conn)
248 except BaseException:
249 resp.close()
250 raise
251 except BaseException:
252 conn.close()
253 raise
254 return resp
257@final
258class ClientSession:
259 """First-class interface for making HTTP requests."""
261 __slots__ = (
262 "_base_url",
263 "_base_url_origin",
264 "_source_traceback",
265 "_connector",
266 "_loop",
267 "_cookie_jar",
268 "_connector_owner",
269 "_version",
270 "_json_serialize",
271 "_json_serialize_bytes",
272 "_requote_redirect_url",
273 "_timeout",
274 "_raise_for_status",
275 "_auto_decompress",
276 "_trust_env",
277 "_default_headers",
278 "_skip_auto_headers",
279 "_request_class",
280 "_response_class",
281 "_ws_response_class",
282 "_trace_configs",
283 "_read_bufsize",
284 "_max_line_size",
285 "_max_field_size",
286 "_max_headers",
287 "_resolve_charset",
288 "_default_proxy",
289 "_default_ssl",
290 "_retry_connection",
291 "_middlewares",
292 )
294 def __init__(
295 self,
296 base_url: StrOrURL | None = None,
297 *,
298 connector: BaseConnector | None = None,
299 cookies: LooseCookies | None = None,
300 headers: LooseHeaders | None = None,
301 proxy: StrOrURL | None = None,
302 ssl: SSLContext | bool | Fingerprint = True,
303 skip_auto_headers: Iterable[str] | None = None,
304 json_serialize: JSONEncoder = json.dumps,
305 json_serialize_bytes: JSONBytesEncoder | None = None,
306 request_class: type[ClientRequest] = ClientRequest,
307 response_class: type[ClientResponse] = ClientResponse,
308 ws_response_class: type[ClientWebSocketResponse] = ClientWebSocketResponse,
309 version: HttpVersion = http.HttpVersion11,
310 cookie_jar: AbstractCookieJar | None = None,
311 connector_owner: bool = True,
312 raise_for_status: bool | Callable[[ClientResponse], Awaitable[None]] = False,
313 timeout: _SENTINEL | ClientTimeout | None = sentinel,
314 auto_decompress: bool = True,
315 trust_env: bool = False,
316 requote_redirect_url: bool = True,
317 trace_configs: list[TraceConfig[object]] | None = None,
318 read_bufsize: int = DEFAULT_CHUNK_SIZE,
319 max_line_size: int = 8190,
320 max_field_size: int = 8190,
321 max_headers: int = 128,
322 fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8",
323 middlewares: Sequence[ClientMiddlewareType] = (),
324 ssl_shutdown_timeout: _SENTINEL | None | float = sentinel,
325 ) -> None:
326 # We initialise _connector to None immediately, as it's referenced in __del__()
327 # and could cause issues if an exception occurs during initialisation.
328 self._connector: BaseConnector | None = None
329 if base_url is None or isinstance(base_url, URL):
330 self._base_url: URL | None = base_url
331 self._base_url_origin = None if base_url is None else base_url.origin()
332 else:
333 self._base_url = URL(base_url)
334 self._base_url_origin = self._base_url.origin()
335 assert self._base_url.absolute, "Only absolute URLs are supported"
336 if self._base_url is not None and not self._base_url.path.endswith("/"):
337 raise ValueError("base_url must have a trailing '/'")
339 if not isinstance(ssl, SSL_ALLOWED_TYPES):
340 raise TypeError(
341 "ssl should be SSLContext, Fingerprint, or bool, "
342 f"got {ssl!r} instead."
343 )
345 loop = asyncio.get_running_loop()
347 if timeout is sentinel or timeout is None:
348 timeout = ClientTimeout()
349 if not isinstance(timeout, ClientTimeout):
350 raise ValueError(
351 f"timeout parameter cannot be of {type(timeout)} type, "
352 "please use 'timeout=ClientTimeout(...)'",
353 )
354 self._timeout = timeout
356 if ssl_shutdown_timeout is not sentinel:
357 warnings.warn(
358 "The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0",
359 DeprecationWarning,
360 stacklevel=2,
361 )
363 if connector is None:
364 connector = TCPConnector(ssl_shutdown_timeout=ssl_shutdown_timeout)
365 # Initialize these three attrs before raising any exception,
366 # they are used in __del__
367 self._connector = connector
368 self._loop = loop
369 if loop.get_debug():
370 self._source_traceback: traceback.StackSummary | None = (
371 traceback.extract_stack(sys._getframe(1))
372 )
373 else:
374 self._source_traceback = None
376 if connector._loop is not loop:
377 raise RuntimeError("Session and connector have to use same event loop")
379 if cookie_jar is None:
380 cookie_jar = CookieJar()
381 self._cookie_jar = cookie_jar
383 if cookies:
384 self._cookie_jar.update_cookies(cookies)
386 self._connector_owner = connector_owner
387 self._version = version
388 self._json_serialize = json_serialize
389 self._json_serialize_bytes = json_serialize_bytes
390 self._raise_for_status = raise_for_status
391 self._auto_decompress = auto_decompress
392 self._trust_env = trust_env
393 self._requote_redirect_url = requote_redirect_url
394 self._read_bufsize = read_bufsize
395 self._max_line_size = max_line_size
396 self._max_field_size = max_field_size
397 self._max_headers = max_headers
399 # Convert to list of tuples
400 if headers:
401 real_headers: CIMultiDict[str] = CIMultiDict(headers)
402 else:
403 real_headers = CIMultiDict()
404 self._default_headers: CIMultiDict[str] = real_headers
405 if skip_auto_headers is not None:
406 self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers)
407 else:
408 self._skip_auto_headers = frozenset()
410 self._request_class = request_class
411 self._response_class = response_class
412 self._ws_response_class = ws_response_class
414 self._trace_configs = trace_configs or []
415 for trace_config in self._trace_configs:
416 trace_config.freeze()
418 self._resolve_charset = fallback_charset_resolver
420 self._default_proxy = proxy
421 self._default_ssl = ssl
422 self._retry_connection: bool = True
423 self._middlewares = tuple(middlewares)
425 def __init_subclass__(cls: type["ClientSession"]) -> None:
426 raise TypeError(
427 f"Inheritance class {cls.__name__} from ClientSession is forbidden"
428 )
430 def __del__(self, _warnings: Any = warnings) -> None:
431 if not self.closed:
432 _warnings.warn(
433 f"Unclosed client session {self!r}",
434 ResourceWarning,
435 source=self,
436 )
437 context = {"client_session": self, "message": "Unclosed client session"}
438 if self._source_traceback is not None:
439 context["source_traceback"] = self._source_traceback
440 self._loop.call_exception_handler(context)
442 if sys.version_info >= (3, 11) and TYPE_CHECKING:
444 def request(
445 self,
446 method: str,
447 url: StrOrURL,
448 **kwargs: Unpack[_RequestOptions],
449 ) -> "_RequestContextManager": ...
451 else:
453 def request(
454 self, method: str, url: StrOrURL, **kwargs: Any
455 ) -> "_RequestContextManager":
456 """Perform HTTP request."""
457 return _RequestContextManager(self._request(method, url, **kwargs))
459 def _build_url(self, str_or_url: StrOrURL) -> URL:
460 url = URL(str_or_url)
461 if self._base_url and not url.absolute:
462 return self._base_url.join(url)
463 return url
465 async def _request(
466 self,
467 method: str,
468 str_or_url: StrOrURL,
469 *,
470 params: Query = None,
471 data: Any = None,
472 json: Any = None,
473 cookies: LooseCookies | None = None,
474 headers: LooseHeaders | None = None,
475 skip_auto_headers: Iterable[str] | None = None,
476 allow_redirects: bool = True,
477 max_redirects: int = 10,
478 compress: Literal["deflate", "gzip"] | bool = False,
479 chunked: bool | None = None,
480 expect100: bool = False,
481 raise_for_status: (
482 None | bool | Callable[[ClientResponse], Awaitable[None]]
483 ) = None,
484 read_until_eof: bool = True,
485 proxy: StrOrURL | None = None,
486 timeout: ClientTimeout | _SENTINEL | None = sentinel,
487 ssl: SSLContext | bool | Fingerprint | _SENTINEL = sentinel,
488 server_hostname: str | None = None,
489 proxy_headers: LooseHeaders | None = None,
490 trace_request_ctx: object = None,
491 read_bufsize: int | None = None,
492 auto_decompress: bool | None = None,
493 max_line_size: int | None = None,
494 max_field_size: int | None = None,
495 max_headers: int | None = None,
496 middlewares: Sequence[ClientMiddlewareType] | None = None,
497 ) -> ClientResponse:
498 # NOTE: timeout clamps existing connect and read timeouts. We cannot
499 # set the default to None because we need to detect if the user wants
500 # to use the existing timeouts by setting timeout to None.
502 if self.closed:
503 raise RuntimeError("Session is closed")
505 method = method.upper()
507 if ssl is sentinel:
508 ssl = self._default_ssl
509 if not isinstance(ssl, SSL_ALLOWED_TYPES):
510 raise TypeError(
511 "ssl should be SSLContext, Fingerprint, or bool, "
512 f"got {ssl!r} instead."
513 )
515 if data is not None and json is not None:
516 raise ValueError(
517 "data and json parameters can not be used at the same time"
518 )
519 elif json is not None:
520 if self._json_serialize_bytes is not None:
521 data = payload.JsonBytesPayload(json, dumps=self._json_serialize_bytes)
522 else:
523 data = payload.JsonPayload(json, dumps=self._json_serialize)
525 redirects = 0
526 history: list[ClientResponse] = []
527 version = self._version
528 params = params or {}
530 # Merge with default headers and transform to CIMultiDict
531 headers = self._prepare_headers(headers)
533 try:
534 url = self._build_url(str_or_url)
535 except ValueError as e:
536 raise InvalidUrlClientError(str_or_url) from e
538 assert self._connector is not None
539 if url.scheme not in self._connector.allowed_protocol_schema_set:
540 raise NonHttpUrlClientError(url)
542 skip_headers: Iterable[istr] | None
543 if skip_auto_headers is not None:
544 skip_headers = {
545 istr(i) for i in skip_auto_headers
546 } | self._skip_auto_headers
547 elif self._skip_auto_headers:
548 skip_headers = self._skip_auto_headers
549 else:
550 skip_headers = None
552 if proxy is None:
553 proxy = self._default_proxy
555 resolved_proxy_headers: CIMultiDict[str] | None
556 if proxy is None:
557 resolved_proxy_headers = None
558 else:
559 resolved_proxy_headers = self._prepare_headers(proxy_headers)
560 try:
561 proxy = URL(proxy)
562 except ValueError as e:
563 raise InvalidURL(proxy) from e
565 if timeout is sentinel or timeout is None:
566 real_timeout: ClientTimeout = self._timeout
567 else:
568 real_timeout = timeout
569 # timeout is cumulative for all request operations
570 # (request, redirects, responses, data consuming)
571 tm = TimeoutHandle(
572 self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold
573 )
574 handle = tm.start()
576 if read_bufsize is None:
577 read_bufsize = self._read_bufsize
579 if auto_decompress is None:
580 auto_decompress = self._auto_decompress
582 if max_line_size is None:
583 max_line_size = self._max_line_size
585 if max_field_size is None:
586 max_field_size = self._max_field_size
588 if max_headers is None:
589 max_headers = self._max_headers
591 traces = [
592 Trace(
593 self,
594 trace_config,
595 trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
596 )
597 for trace_config in self._trace_configs
598 ]
600 for trace in traces:
601 await trace.send_request_start(method, url.update_query(params), headers)
603 timer = tm.timer()
604 req: ClientRequest | None = None
605 resp: ClientResponse | None = None
606 try:
607 with timer:
608 # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests
609 retry_persistent_connection = (
610 self._retry_connection and method in IDEMPOTENT_METHODS
611 )
612 while True:
613 url, auth_from_url = strip_auth_from_url(url)
614 if not url.raw_host:
615 # NOTE: Bail early, otherwise, causes `InvalidURL` through
616 # NOTE: `self._request_class()` below.
617 err_exc_cls = (
618 InvalidUrlRedirectClientError
619 if redirects
620 else InvalidUrlClientError
621 )
622 raise err_exc_cls(url)
624 if auth_from_url is not None:
625 # URL-embedded credentials override any Authorization
626 # header already present (e.g. carried from a previous
627 # redirect). On the initial request, refuse to silently
628 # shadow an explicit Authorization header.
629 if not history and hdrs.AUTHORIZATION in headers:
630 raise ValueError(
631 "Cannot combine AUTHORIZATION header with "
632 "credentials encoded in URL"
633 )
634 headers[hdrs.AUTHORIZATION] = auth_from_url
635 elif (
636 self._trust_env
637 and url.host is not None
638 and hdrs.AUTHORIZATION not in headers
639 ):
640 # Fall back to ~/.netrc credentials when trust_env is set.
641 netrc_auth = await self._loop.run_in_executor(
642 None, self._get_netrc_auth, url.host
643 )
644 if netrc_auth is not None:
645 headers[hdrs.AUTHORIZATION] = netrc_auth
647 all_cookies = self._cookie_jar.filter_cookies(url)
649 if cookies is not None:
650 tmp_cookie_jar = CookieJar(
651 unsafe=self._cookie_jar.unsafe,
652 quote_cookie=self._cookie_jar.quote_cookie,
653 )
654 tmp_cookie_jar.update_cookies(cookies)
655 req_cookies = tmp_cookie_jar.filter_cookies(url)
656 if req_cookies:
657 all_cookies.load(req_cookies)
659 proxy_: URL | None = None
660 if proxy is not None:
661 proxy_ = URL(proxy)
662 elif self._trust_env:
663 # Re-resolve per iteration; drop stale env-proxy auth so
664 # a redirect that switches proxies can't leak credentials.
665 resolved_proxy_headers = None
666 with suppress(LookupError):
667 proxy_, env_proxy_auth = await asyncio.to_thread(
668 get_env_proxy_for_url, url
669 )
670 if env_proxy_auth is not None:
671 resolved_proxy_headers = CIMultiDict(
672 {hdrs.PROXY_AUTHORIZATION: env_proxy_auth}
673 )
675 response_params: ResponseParams = {
676 "timer": timer,
677 "skip_payload": method in EMPTY_BODY_METHODS,
678 "read_until_eof": read_until_eof,
679 "auto_decompress": auto_decompress,
680 "read_timeout": real_timeout.sock_read,
681 "read_bufsize": read_bufsize,
682 "timeout_ceil_threshold": self._connector._timeout_ceil_threshold,
683 "max_line_size": max_line_size,
684 "max_field_size": max_field_size,
685 "max_headers": max_headers,
686 }
688 req = self._request_class(
689 method,
690 url,
691 params=params,
692 headers=headers,
693 skip_auto_headers=skip_headers,
694 data=data,
695 cookies=all_cookies,
696 version=version,
697 compress=compress,
698 chunked=chunked,
699 expect100=expect100,
700 loop=self._loop,
701 response_class=self._response_class,
702 proxy=proxy_,
703 response_params=response_params,
704 timer=timer,
705 timeout=real_timeout,
706 session=self,
707 ssl=ssl,
708 server_hostname=server_hostname,
709 proxy_headers=resolved_proxy_headers,
710 traces=traces,
711 trust_env=self.trust_env,
712 )
714 # Apply middleware (if any) - per-request middleware overrides session middleware
715 effective_middlewares = (
716 self._middlewares if middlewares is None else tuple(middlewares)
717 )
719 if effective_middlewares:
720 handler = _cached_build_client_middlewares(
721 _connect_and_send_request, effective_middlewares
722 )
723 else:
724 handler = _connect_and_send_request
726 try:
727 resp = await handler(req)
728 # Client connector errors should not be retried
729 except (
730 ConnectionTimeoutError,
731 ClientConnectorError,
732 ClientConnectorCertificateError,
733 ClientConnectorSSLError,
734 ):
735 raise
736 except (ClientOSError, ServerDisconnectedError):
737 if not retry_persistent_connection:
738 raise
739 retry_persistent_connection = False
740 if data is not None:
741 # Rebuilding from `data` would resend only the unread
742 # remainder of a file object; reuse the payload, which
743 # rewinds itself once the cancelled writer has settled.
744 await req._close()
745 if req._body.consumed:
746 raise
747 data = req._body
748 continue
749 except ClientError:
750 raise
751 except OSError as exc:
752 if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
753 raise
754 raise ClientOSError(*exc.args) from exc
756 # Update cookies from raw headers to preserve duplicates
757 if resp._raw_cookie_headers:
758 self._cookie_jar.update_cookies_from_headers(
759 resp._raw_cookie_headers, resp.url
760 )
762 # redirects
763 if resp.status in (301, 302, 303, 307, 308) and allow_redirects:
764 for trace in traces:
765 await trace.send_request_redirect(
766 method, url.update_query(params), headers, resp
767 )
769 redirects += 1
770 history.append(resp)
771 if max_redirects and redirects >= max_redirects:
772 if req._body is not None:
773 await req._body.close()
774 resp.close()
775 raise TooManyRedirects(
776 history[0].request_info, tuple(history)
777 )
779 # For 301 and 302, mimic IE, now changed in RFC
780 # https://github.com/kennethreitz/requests/pull/269
781 if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (
782 resp.status in (301, 302) and resp.method == hdrs.METH_POST
783 ):
784 method = hdrs.METH_GET
785 data = None
786 if headers.get(hdrs.CONTENT_LENGTH):
787 headers.pop(hdrs.CONTENT_LENGTH)
788 else:
789 # For 307/308, always preserve the request body
790 # For 301/302 with non-POST methods, preserve the request body
791 # https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1
792 # Use the existing payload to avoid recreating it from
793 # a potentially consumed file.
794 #
795 # If the payload is already consumed and cannot be replayed,
796 # fail fast instead of silently sending an empty body.
797 if req._body.consumed:
798 resp.close()
799 raise ClientPayloadError(
800 "Cannot follow redirect with a consumed request "
801 "body. Use bytes, a seekable file-like object, "
802 "or set allow_redirects=False."
803 )
804 data = req._body
806 r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
807 hdrs.URI
808 )
809 if r_url is None:
810 # see github.com/aio-libs/aiohttp/issues/2022
811 break
812 else:
813 # reading from correct redirection
814 # response is forbidden
815 resp.release()
817 try:
818 parsed_redirect_url = URL(
819 r_url, encoded=not self._requote_redirect_url
820 )
821 except ValueError as e:
822 if req._body is not None:
823 await req._body.close()
824 resp.close()
825 raise InvalidUrlRedirectClientError(
826 r_url,
827 "Server attempted redirecting to a location that does not look like a URL",
828 ) from e
830 scheme = parsed_redirect_url.scheme
831 if scheme not in HTTP_AND_EMPTY_SCHEMA_SET:
832 if req._body is not None:
833 await req._body.close()
834 resp.close()
835 raise NonHttpUrlRedirectClientError(r_url)
836 elif not scheme:
837 parsed_redirect_url = url.join(parsed_redirect_url)
839 try:
840 redirect_origin = parsed_redirect_url.origin()
841 except ValueError as origin_val_err:
842 if req._body is not None:
843 await req._body.close()
844 resp.close()
845 raise InvalidUrlRedirectClientError(
846 parsed_redirect_url,
847 "Invalid redirect URL origin",
848 ) from origin_val_err
850 if url.origin() != redirect_origin:
851 cookies = None
852 headers.popall(hdrs.AUTHORIZATION, None)
853 headers.popall(hdrs.COOKIE, None)
854 headers.popall(hdrs.PROXY_AUTHORIZATION, None)
856 url = parsed_redirect_url
857 params = {}
858 resp.release()
859 continue
861 break
863 if req._body is not None:
864 await req._body.close()
865 # check response status
866 if raise_for_status is None:
867 raise_for_status = self._raise_for_status
869 if callable(raise_for_status):
870 await raise_for_status(resp)
871 elif raise_for_status:
872 resp.raise_for_status()
874 # register connection
875 if handle is not None:
876 if resp.connection is not None:
877 resp.connection.add_callback(handle.cancel)
878 else:
879 handle.cancel()
881 resp._history = tuple(history)
883 for trace in traces:
884 await trace.send_request_end(
885 method, url.update_query(params), headers, resp
886 )
887 return resp
889 except BaseException as e:
890 # cleanup timer
891 tm.close()
892 if handle:
893 handle.cancel()
894 handle = None
896 if resp is not None:
897 # A failure occurred after the response was received.
898 resp.close()
900 if req is not None and req._body is not None:
901 await req._body.close()
903 for trace in traces:
904 await trace.send_request_exception(
905 method, url.update_query(params), headers, e
906 )
907 raise
909 if sys.version_info >= (3, 11) and TYPE_CHECKING:
911 @overload
912 def ws_connect(
913 self,
914 url: StrOrURL,
915 *,
916 decode_text: Literal[True] = ...,
917 **kwargs: Unpack[_WSConnectOptions],
918 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[Literal[True]]]": ...
920 @overload
921 def ws_connect(
922 self,
923 url: StrOrURL,
924 *,
925 decode_text: Literal[False],
926 **kwargs: Unpack[_WSConnectOptions],
927 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[Literal[False]]]": ...
929 @overload
930 def ws_connect(
931 self,
932 url: StrOrURL,
933 *,
934 decode_text: bool = ...,
935 **kwargs: Unpack[_WSConnectOptions],
936 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[bool]]": ...
938 def ws_connect(
939 self,
940 url: StrOrURL,
941 *,
942 method: str = hdrs.METH_GET,
943 protocols: Collection[str] = (),
944 timeout: ClientWSTimeout | _SENTINEL = sentinel,
945 receive_timeout: float | None = None,
946 autoclose: bool = True,
947 autoping: bool = True,
948 heartbeat: float | None = None,
949 origin: str | None = None,
950 params: Query = None,
951 headers: LooseHeaders | None = None,
952 proxy: StrOrURL | None = None,
953 ssl: SSLContext | bool | Fingerprint | _SENTINEL = sentinel,
954 server_hostname: str | None = None,
955 proxy_headers: LooseHeaders | None = None,
956 compress: int = 0,
957 max_msg_size: int = 4 * 1024 * 1024,
958 decode_text: bool = True,
959 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[bool]]":
960 """Initiate websocket connection."""
961 return _WSRequestContextManager(
962 self._ws_connect(
963 url,
964 method=method,
965 protocols=protocols,
966 timeout=timeout,
967 receive_timeout=receive_timeout,
968 autoclose=autoclose,
969 autoping=autoping,
970 heartbeat=heartbeat,
971 origin=origin,
972 params=params,
973 headers=headers,
974 proxy=proxy,
975 ssl=ssl,
976 server_hostname=server_hostname,
977 proxy_headers=proxy_headers,
978 compress=compress,
979 max_msg_size=max_msg_size,
980 decode_text=decode_text,
981 )
982 )
984 if sys.version_info >= (3, 11) and TYPE_CHECKING:
986 @overload
987 async def _ws_connect(
988 self,
989 url: StrOrURL,
990 *,
991 decode_text: Literal[True] = ...,
992 **kwargs: Unpack[_WSConnectOptions],
993 ) -> "ClientWebSocketResponse[Literal[True]]": ...
995 @overload
996 async def _ws_connect(
997 self,
998 url: StrOrURL,
999 *,
1000 decode_text: Literal[False],
1001 **kwargs: Unpack[_WSConnectOptions],
1002 ) -> "ClientWebSocketResponse[Literal[False]]": ...
1004 @overload
1005 async def _ws_connect(
1006 self,
1007 url: StrOrURL,
1008 *,
1009 decode_text: bool = ...,
1010 **kwargs: Unpack[_WSConnectOptions],
1011 ) -> "ClientWebSocketResponse[bool]": ...
1013 async def _ws_connect(
1014 self,
1015 url: StrOrURL,
1016 *,
1017 method: str = hdrs.METH_GET,
1018 protocols: Collection[str] = (),
1019 timeout: ClientWSTimeout | _SENTINEL = sentinel,
1020 receive_timeout: float | None = None,
1021 autoclose: bool = True,
1022 autoping: bool = True,
1023 heartbeat: float | None = None,
1024 origin: str | None = None,
1025 params: Query = None,
1026 headers: LooseHeaders | None = None,
1027 proxy: StrOrURL | None = None,
1028 ssl: SSLContext | bool | Fingerprint | _SENTINEL = sentinel,
1029 server_hostname: str | None = None,
1030 proxy_headers: LooseHeaders | None = None,
1031 compress: int = 0,
1032 max_msg_size: int = 4 * 1024 * 1024,
1033 decode_text: bool = True,
1034 ) -> "ClientWebSocketResponse[bool]":
1035 if timeout is not sentinel:
1036 if isinstance(timeout, ClientWSTimeout):
1037 ws_timeout = timeout
1038 else:
1039 warnings.warn( # type: ignore[unreachable]
1040 "parameter 'timeout' of type 'float' "
1041 "is deprecated, please use "
1042 "'timeout=ClientWSTimeout(ws_close=...)'",
1043 DeprecationWarning,
1044 stacklevel=2,
1045 )
1046 ws_timeout = ClientWSTimeout(ws_close=timeout)
1047 else:
1048 ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT
1049 if receive_timeout is not None:
1050 warnings.warn(
1051 "float parameter 'receive_timeout' "
1052 "is deprecated, please use parameter "
1053 "'timeout=ClientWSTimeout(ws_receive=...)'",
1054 DeprecationWarning,
1055 stacklevel=2,
1056 )
1057 ws_timeout = dataclasses.replace(ws_timeout, ws_receive=receive_timeout)
1059 if headers is None:
1060 real_headers: CIMultiDict[str] = CIMultiDict()
1061 else:
1062 real_headers = CIMultiDict(headers)
1064 default_headers = {
1065 hdrs.UPGRADE: "websocket",
1066 hdrs.CONNECTION: "Upgrade",
1067 hdrs.SEC_WEBSOCKET_VERSION: "13",
1068 }
1070 for key, value in default_headers.items():
1071 real_headers.setdefault(key, value)
1073 sec_key = base64.b64encode(os.urandom(16))
1074 real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()
1076 if protocols:
1077 real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols)
1078 if origin is not None:
1079 real_headers[hdrs.ORIGIN] = origin
1080 if compress:
1081 extstr = ws_ext_gen(compress=compress)
1082 real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr
1084 if ssl is not sentinel and not isinstance(ssl, SSL_ALLOWED_TYPES):
1085 raise TypeError(
1086 "ssl should be SSLContext, Fingerprint, or bool, "
1087 f"got {ssl!r} instead."
1088 )
1090 # send request
1091 resp = await self.request(
1092 method,
1093 url,
1094 params=params,
1095 headers=real_headers,
1096 read_until_eof=False,
1097 proxy=proxy,
1098 ssl=ssl,
1099 server_hostname=server_hostname,
1100 proxy_headers=proxy_headers,
1101 )
1103 try:
1104 # check handshake
1105 if resp.status != 101:
1106 raise WSServerHandshakeError(
1107 resp.request_info,
1108 resp.history,
1109 message="Invalid response status",
1110 status=resp.status,
1111 headers=resp.headers,
1112 )
1114 if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket":
1115 raise WSServerHandshakeError(
1116 resp.request_info,
1117 resp.history,
1118 message="Invalid upgrade header",
1119 status=resp.status,
1120 headers=resp.headers,
1121 )
1123 if not resp._upgraded:
1124 raise WSServerHandshakeError(
1125 resp.request_info,
1126 resp.history,
1127 message="Invalid connection header",
1128 status=resp.status,
1129 headers=resp.headers,
1130 )
1132 # key calculation
1133 r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")
1134 match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
1135 if r_key != match:
1136 raise WSServerHandshakeError(
1137 resp.request_info,
1138 resp.history,
1139 message="Invalid challenge response",
1140 status=resp.status,
1141 headers=resp.headers,
1142 )
1144 # websocket protocol
1145 protocol = None
1146 if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:
1147 resp_protocols = [
1148 proto.strip()
1149 for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
1150 ]
1152 for proto in resp_protocols:
1153 if proto in protocols:
1154 protocol = proto
1155 break
1157 # websocket compress
1158 notakeover = False
1159 if compress:
1160 compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)
1161 if compress_hdrs:
1162 try:
1163 compress, notakeover = ws_ext_parse(compress_hdrs)
1164 except WSHandshakeError as exc:
1165 raise WSServerHandshakeError(
1166 resp.request_info,
1167 resp.history,
1168 message=exc.args[0],
1169 status=resp.status,
1170 headers=resp.headers,
1171 ) from exc
1172 else:
1173 compress = 0
1174 notakeover = False
1176 conn = resp.connection
1177 assert conn is not None
1178 conn_proto = conn.protocol
1179 assert conn_proto is not None
1181 # For WS connection the read_timeout must be either ws_timeout.ws_receive or greater
1182 # None == no timeout, i.e. infinite timeout, so None is the max timeout possible
1183 if ws_timeout.ws_receive is None:
1184 # Reset regardless
1185 conn_proto.read_timeout = None
1186 elif conn_proto.read_timeout is not None:
1187 conn_proto.read_timeout = max(
1188 ws_timeout.ws_receive, conn_proto.read_timeout
1189 )
1191 transport = conn.transport
1192 assert transport is not None
1193 reader = WebSocketDataQueue(conn_proto, DEFAULT_CHUNK_SIZE, loop=self._loop)
1194 writer = WebSocketWriter(
1195 conn_proto,
1196 transport,
1197 use_mask=True,
1198 compress=compress,
1199 notakeover=notakeover,
1200 )
1201 except BaseException:
1202 resp.close()
1203 raise
1204 else:
1205 ws_resp = self._ws_response_class(
1206 reader,
1207 writer,
1208 protocol,
1209 resp,
1210 ws_timeout,
1211 autoclose,
1212 autoping,
1213 self._loop,
1214 heartbeat=heartbeat,
1215 compress=compress,
1216 client_notakeover=notakeover,
1217 )
1218 ws_resp._parser = WebSocketReader(
1219 reader,
1220 max_msg_size,
1221 compress=bool(compress),
1222 decode_text=decode_text,
1223 )
1224 cb = None if heartbeat is None else ws_resp._on_data_received
1225 conn_proto.set_parser(ws_resp._parser, reader, data_received_cb=cb)
1226 return ws_resp
1228 def _prepare_headers(self, headers: LooseHeaders | None) -> "CIMultiDict[str]":
1229 """Add default headers and transform it to CIMultiDict"""
1230 # Convert headers to MultiDict
1231 result = CIMultiDict(self._default_headers)
1232 if headers:
1233 if not isinstance(headers, (MultiDictProxy, MultiDict)):
1234 headers = CIMultiDict(headers)
1235 added_names: set[str] = set()
1236 for key, value in headers.items():
1237 if key in added_names:
1238 result.add(key, value)
1239 else:
1240 result[key] = value
1241 added_names.add(key)
1242 return result
1244 def _get_netrc_auth(self, host: str) -> str | None:
1245 """Return an ``Authorization`` header value for ``host`` from netrc.
1247 Designed to be called in an executor to avoid blocking I/O on the
1248 event loop.
1249 """
1250 netrc_obj = netrc_from_env()
1251 try:
1252 return _auth_header_from_netrc(netrc_obj, host)
1253 except LookupError:
1254 return None
1256 if sys.version_info >= (3, 11) and TYPE_CHECKING:
1258 def get(
1259 self,
1260 url: StrOrURL,
1261 **kwargs: Unpack[_RequestOptions],
1262 ) -> "_RequestContextManager": ...
1264 def options(
1265 self,
1266 url: StrOrURL,
1267 **kwargs: Unpack[_RequestOptions],
1268 ) -> "_RequestContextManager": ...
1270 def head(
1271 self,
1272 url: StrOrURL,
1273 **kwargs: Unpack[_RequestOptions],
1274 ) -> "_RequestContextManager": ...
1276 def post(
1277 self,
1278 url: StrOrURL,
1279 **kwargs: Unpack[_RequestOptions],
1280 ) -> "_RequestContextManager": ...
1282 def put(
1283 self,
1284 url: StrOrURL,
1285 **kwargs: Unpack[_RequestOptions],
1286 ) -> "_RequestContextManager": ...
1288 def patch(
1289 self,
1290 url: StrOrURL,
1291 **kwargs: Unpack[_RequestOptions],
1292 ) -> "_RequestContextManager": ...
1294 def delete(
1295 self,
1296 url: StrOrURL,
1297 **kwargs: Unpack[_RequestOptions],
1298 ) -> "_RequestContextManager": ...
1300 else:
1302 def get(
1303 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
1304 ) -> "_RequestContextManager":
1305 """Perform HTTP GET request."""
1306 return _RequestContextManager(
1307 self._request(
1308 hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs
1309 )
1310 )
1312 def options(
1313 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
1314 ) -> "_RequestContextManager":
1315 """Perform HTTP OPTIONS request."""
1316 return _RequestContextManager(
1317 self._request(
1318 hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs
1319 )
1320 )
1322 def head(
1323 self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any
1324 ) -> "_RequestContextManager":
1325 """Perform HTTP HEAD request."""
1326 return _RequestContextManager(
1327 self._request(
1328 hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs
1329 )
1330 )
1332 def post(
1333 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1334 ) -> "_RequestContextManager":
1335 """Perform HTTP POST request."""
1336 return _RequestContextManager(
1337 self._request(hdrs.METH_POST, url, data=data, **kwargs)
1338 )
1340 def put(
1341 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1342 ) -> "_RequestContextManager":
1343 """Perform HTTP PUT request."""
1344 return _RequestContextManager(
1345 self._request(hdrs.METH_PUT, url, data=data, **kwargs)
1346 )
1348 def patch(
1349 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1350 ) -> "_RequestContextManager":
1351 """Perform HTTP PATCH request."""
1352 return _RequestContextManager(
1353 self._request(hdrs.METH_PATCH, url, data=data, **kwargs)
1354 )
1356 def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager":
1357 """Perform HTTP DELETE request."""
1358 return _RequestContextManager(
1359 self._request(hdrs.METH_DELETE, url, **kwargs)
1360 )
1362 async def close(self) -> None:
1363 """Close underlying connector.
1365 Release all acquired resources.
1366 """
1367 if not self.closed:
1368 if self._connector is not None and self._connector_owner:
1369 await self._connector.close()
1370 self._connector = None
1372 @property
1373 def closed(self) -> bool:
1374 """Is client session closed.
1376 A readonly property.
1377 """
1378 return self._connector is None or self._connector.closed
1380 @property
1381 def connector(self) -> BaseConnector | None:
1382 """Connector instance used for the session."""
1383 return self._connector
1385 @property
1386 def cookie_jar(self) -> AbstractCookieJar:
1387 """The session cookies."""
1388 return self._cookie_jar
1390 @property
1391 def version(self) -> tuple[int, int]:
1392 """The session HTTP protocol version."""
1393 return self._version
1395 @property
1396 def requote_redirect_url(self) -> bool:
1397 """Do URL requoting on redirection handling."""
1398 return self._requote_redirect_url
1400 @property
1401 def timeout(self) -> ClientTimeout:
1402 """Timeout for the session."""
1403 return self._timeout
1405 @property
1406 def headers(self) -> "CIMultiDict[str]":
1407 """The default headers of the client session."""
1408 return self._default_headers
1410 @property
1411 def skip_auto_headers(self) -> frozenset[istr]:
1412 """Headers for which autogeneration should be skipped"""
1413 return self._skip_auto_headers
1415 @property
1416 def json_serialize(self) -> JSONEncoder:
1417 """Json serializer callable"""
1418 return self._json_serialize
1420 @property
1421 def connector_owner(self) -> bool:
1422 """Should connector be closed on session closing"""
1423 return self._connector_owner
1425 @property
1426 def raise_for_status(
1427 self,
1428 ) -> bool | Callable[[ClientResponse], Awaitable[None]]:
1429 """Should `ClientResponse.raise_for_status()` be called for each response."""
1430 return self._raise_for_status
1432 @property
1433 def auto_decompress(self) -> bool:
1434 """Should the body response be automatically decompressed."""
1435 return self._auto_decompress
1437 @property
1438 def trust_env(self) -> bool:
1439 """
1440 Should proxies information from environment or netrc be trusted.
1442 Information is from HTTP_PROXY / HTTPS_PROXY environment variables
1443 or ~/.netrc file if present.
1444 """
1445 return self._trust_env
1447 @property
1448 def trace_configs(self) -> list[TraceConfig[Any]]:
1449 """A list of TraceConfig instances used for client tracing"""
1450 return self._trace_configs
1452 def detach(self) -> None:
1453 """Detach connector from session without closing the former.
1455 Session is switched to closed state anyway.
1456 """
1457 self._connector = None
1459 async def __aenter__(self) -> "ClientSession":
1460 return self
1462 async def __aexit__(
1463 self,
1464 exc_type: type[BaseException] | None,
1465 exc_val: BaseException | None,
1466 exc_tb: TracebackType | None,
1467 ) -> None:
1468 await self.close()
1471class _BaseRequestContextManager(
1472 Coroutine[Any, Any, _RetType_co], Generic[_RetType_co]
1473):
1474 __slots__ = ("_coro", "_resp")
1476 def __init__(self, coro: Coroutine[asyncio.Future[Any], None, _RetType_co]) -> None:
1477 self._coro: Coroutine[asyncio.Future[Any], None, _RetType_co] = coro
1479 def send(self, arg: None) -> asyncio.Future[Any]:
1480 return self._coro.send(arg)
1482 def throw(self, *args: Any, **kwargs: Any) -> asyncio.Future[Any]:
1483 return self._coro.throw(*args, **kwargs)
1485 def close(self) -> None:
1486 return self._coro.close()
1488 def __await__(self) -> Generator[Any, None, _RetType_co]:
1489 ret = self._coro.__await__()
1490 return ret
1492 def __iter__(self) -> Generator[Any, None, _RetType_co]:
1493 return self.__await__()
1495 async def __aenter__(self) -> _RetType_co:
1496 self._resp: _RetType_co = await self._coro
1497 return await self._resp.__aenter__() # type: ignore[return-value]
1499 async def __aexit__(
1500 self,
1501 exc_type: type[BaseException] | None,
1502 exc: BaseException | None,
1503 tb: TracebackType | None,
1504 ) -> None:
1505 await self._resp.__aexit__(exc_type, exc, tb)
1508_RequestContextManager = _BaseRequestContextManager[ClientResponse]
1509_WSRequestContextManager = _BaseRequestContextManager[ClientWebSocketResponse[bool]]
1512class _SessionRequestContextManager:
1513 __slots__ = ("_coro", "_resp", "_session")
1515 def __init__(
1516 self,
1517 coro: Coroutine[asyncio.Future[Any], None, ClientResponse],
1518 session: ClientSession,
1519 ) -> None:
1520 self._coro = coro
1521 self._resp: ClientResponse | None = None
1522 self._session = session
1524 async def __aenter__(self) -> ClientResponse:
1525 try:
1526 self._resp = await self._coro
1527 except BaseException:
1528 await self._session.close()
1529 raise
1530 else:
1531 return self._resp
1533 async def __aexit__(
1534 self,
1535 exc_type: type[BaseException] | None,
1536 exc: BaseException | None,
1537 tb: TracebackType | None,
1538 ) -> None:
1539 assert self._resp is not None
1540 self._resp.close()
1541 await self._session.close()
1544if sys.version_info >= (3, 11) and TYPE_CHECKING:
1546 def request(
1547 method: str,
1548 url: StrOrURL,
1549 *,
1550 version: HttpVersion = http.HttpVersion11,
1551 connector: BaseConnector | None = None,
1552 **kwargs: Unpack[_RequestOptions],
1553 ) -> _SessionRequestContextManager: ...
1555else:
1557 def request(
1558 method: str,
1559 url: StrOrURL,
1560 *,
1561 version: HttpVersion = http.HttpVersion11,
1562 connector: BaseConnector | None = None,
1563 **kwargs: Any,
1564 ) -> _SessionRequestContextManager:
1565 """Constructs and sends a request.
1567 Returns response object.
1568 method - HTTP method
1569 url - request url
1570 params - (optional) Dictionary or bytes to be sent in the query
1571 string of the new request
1572 data - (optional) Dictionary, bytes, or file-like object to
1573 send in the body of the request
1574 json - (optional) Any json compatible python object
1575 headers - (optional) Dictionary of HTTP Headers to send with
1576 the request
1577 cookies - (optional) Dict object to send with the request
1578 allow_redirects - (optional) If set to False, do not follow
1579 redirects
1580 version - Request HTTP version.
1581 compress - Set to True if request has to be compressed
1582 with deflate encoding.
1583 chunked - Set to chunk size for chunked transfer encoding.
1584 expect100 - Expect 100-continue response from server.
1585 connector - BaseConnector sub-class instance to support
1586 connection pooling.
1587 read_until_eof - Read response until eof if response
1588 does not have Content-Length header.
1589 loop - Optional event loop.
1590 timeout - Optional ClientTimeout settings structure, 5min
1591 total timeout by default.
1592 Usage::
1593 >>> import aiohttp
1594 >>> async with aiohttp.request('GET', 'http://python.org/') as resp:
1595 ... print(resp)
1596 ... data = await resp.read()
1597 <ClientResponse(https://www.python.org/) [200 OK]>
1598 """
1599 connector_owner = False
1600 if connector is None:
1601 connector_owner = True
1602 connector = TCPConnector(force_close=True)
1604 session = ClientSession(
1605 cookies=kwargs.pop("cookies", None),
1606 version=version,
1607 timeout=kwargs.pop("timeout", sentinel),
1608 connector=connector,
1609 connector_owner=connector_owner,
1610 )
1612 return _SessionRequestContextManager(
1613 session._request(method, url, **kwargs),
1614 session,
1615 )