Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/aiohttp/client.py: 53%
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 hashlib
6import json
7import os
8import sys
9import traceback
10import warnings
11from contextlib import suppress
12from types import TracebackType
13from typing import (
14 TYPE_CHECKING,
15 Any,
16 Awaitable,
17 Callable,
18 Coroutine,
19 Final,
20 FrozenSet,
21 Generator,
22 Generic,
23 Iterable,
24 List,
25 Mapping,
26 Optional,
27 Sequence,
28 Set,
29 Tuple,
30 Type,
31 TypedDict,
32 TypeVar,
33 Union,
34)
36import attr
37from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr
38from yarl import URL
40from . import hdrs, http, payload
41from ._websocket.reader import WebSocketDataQueue
42from .abc import AbstractCookieJar
43from .client_exceptions import (
44 ClientConnectionError,
45 ClientConnectionResetError,
46 ClientConnectorCertificateError,
47 ClientConnectorDNSError,
48 ClientConnectorError,
49 ClientConnectorSSLError,
50 ClientError,
51 ClientHttpProxyError,
52 ClientOSError,
53 ClientPayloadError,
54 ClientProxyConnectionError,
55 ClientResponseError,
56 ClientSSLError,
57 ConnectionTimeoutError,
58 ContentTypeError,
59 InvalidURL,
60 InvalidUrlClientError,
61 InvalidUrlRedirectClientError,
62 NonHttpUrlClientError,
63 NonHttpUrlRedirectClientError,
64 RedirectClientError,
65 ServerConnectionError,
66 ServerDisconnectedError,
67 ServerFingerprintMismatch,
68 ServerTimeoutError,
69 SocketTimeoutError,
70 TooManyRedirects,
71 WSMessageTypeError,
72 WSServerHandshakeError,
73)
74from .client_middlewares import ClientMiddlewareType, build_client_middlewares
75from .client_reqrep import (
76 ClientRequest as ClientRequest,
77 ClientResponse as ClientResponse,
78 Fingerprint as Fingerprint,
79 RequestInfo as RequestInfo,
80 _merge_ssl_params,
81)
82from .client_ws import (
83 DEFAULT_WS_CLIENT_TIMEOUT,
84 ClientWebSocketResponse as ClientWebSocketResponse,
85 ClientWSTimeout as ClientWSTimeout,
86)
87from .connector import (
88 HTTP_AND_EMPTY_SCHEMA_SET,
89 BaseConnector as BaseConnector,
90 NamedPipeConnector as NamedPipeConnector,
91 TCPConnector as TCPConnector,
92 UnixConnector as UnixConnector,
93)
94from .cookiejar import CookieJar
95from .helpers import (
96 _SENTINEL,
97 DEBUG,
98 EMPTY_BODY_METHODS,
99 BasicAuth,
100 TimeoutHandle,
101 basicauth_from_netrc,
102 get_env_proxy_for_url,
103 netrc_from_env,
104 sentinel,
105 strip_auth_from_url,
106)
107from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter
108from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse
109from .tracing import Trace, TraceConfig
110from .typedefs import JSONEncoder, LooseCookies, LooseHeaders, Query, StrOrURL
112__all__ = (
113 # client_exceptions
114 "ClientConnectionError",
115 "ClientConnectionResetError",
116 "ClientConnectorCertificateError",
117 "ClientConnectorDNSError",
118 "ClientConnectorError",
119 "ClientConnectorSSLError",
120 "ClientError",
121 "ClientHttpProxyError",
122 "ClientOSError",
123 "ClientPayloadError",
124 "ClientProxyConnectionError",
125 "ClientResponseError",
126 "ClientSSLError",
127 "ConnectionTimeoutError",
128 "ContentTypeError",
129 "InvalidURL",
130 "InvalidUrlClientError",
131 "RedirectClientError",
132 "NonHttpUrlClientError",
133 "InvalidUrlRedirectClientError",
134 "NonHttpUrlRedirectClientError",
135 "ServerConnectionError",
136 "ServerDisconnectedError",
137 "ServerFingerprintMismatch",
138 "ServerTimeoutError",
139 "SocketTimeoutError",
140 "TooManyRedirects",
141 "WSServerHandshakeError",
142 # client_reqrep
143 "ClientRequest",
144 "ClientResponse",
145 "Fingerprint",
146 "RequestInfo",
147 # connector
148 "BaseConnector",
149 "TCPConnector",
150 "UnixConnector",
151 "NamedPipeConnector",
152 # client_ws
153 "ClientWebSocketResponse",
154 # client
155 "ClientSession",
156 "ClientTimeout",
157 "ClientWSTimeout",
158 "request",
159 "WSMessageTypeError",
160)
163if TYPE_CHECKING:
164 from ssl import SSLContext
165else:
166 SSLContext = None
168if sys.version_info >= (3, 11) and TYPE_CHECKING:
169 from typing import Unpack
172class _RequestOptions(TypedDict, total=False):
173 params: Query
174 data: Any
175 json: Any
176 cookies: Union[LooseCookies, None]
177 headers: Union[LooseHeaders, None]
178 skip_auto_headers: Union[Iterable[str], None]
179 auth: Union[BasicAuth, None]
180 allow_redirects: bool
181 max_redirects: int
182 compress: Union[str, bool, None]
183 chunked: Union[bool, None]
184 expect100: bool
185 raise_for_status: Union[None, bool, Callable[[ClientResponse], Awaitable[None]]]
186 read_until_eof: bool
187 proxy: Union[StrOrURL, None]
188 proxy_auth: Union[BasicAuth, None]
189 timeout: "Union[ClientTimeout, _SENTINEL, None]"
190 ssl: Union[SSLContext, bool, Fingerprint]
191 server_hostname: Union[str, None]
192 proxy_headers: Union[LooseHeaders, None]
193 trace_request_ctx: Union[Mapping[str, Any], None]
194 read_bufsize: Union[int, None]
195 auto_decompress: Union[bool, None]
196 max_line_size: Union[int, None]
197 max_field_size: Union[int, None]
198 middlewares: Optional[Sequence[ClientMiddlewareType]]
201@attr.s(auto_attribs=True, frozen=True, slots=True)
202class ClientTimeout:
203 total: Optional[float] = None
204 connect: Optional[float] = None
205 sock_read: Optional[float] = None
206 sock_connect: Optional[float] = None
207 ceil_threshold: float = 5
209 # pool_queue_timeout: Optional[float] = None
210 # dns_resolution_timeout: Optional[float] = None
211 # socket_connect_timeout: Optional[float] = None
212 # connection_acquiring_timeout: Optional[float] = None
213 # new_connection_timeout: Optional[float] = None
214 # http_header_timeout: Optional[float] = None
215 # response_body_timeout: Optional[float] = None
217 # to create a timeout specific for a single request, either
218 # - create a completely new one to overwrite the default
219 # - or use http://www.attrs.org/en/stable/api.html#attr.evolve
220 # to overwrite the defaults
223# 5 Minute default read timeout
224DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60, sock_connect=30)
226# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2
227IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"})
229_RetType = TypeVar("_RetType", ClientResponse, ClientWebSocketResponse)
230_CharsetResolver = Callable[[ClientResponse, bytes], str]
233class ClientSession:
234 """First-class interface for making HTTP requests."""
236 ATTRS = frozenset(
237 [
238 "_base_url",
239 "_base_url_origin",
240 "_source_traceback",
241 "_connector",
242 "_loop",
243 "_cookie_jar",
244 "_connector_owner",
245 "_default_auth",
246 "_version",
247 "_json_serialize",
248 "_requote_redirect_url",
249 "_timeout",
250 "_raise_for_status",
251 "_auto_decompress",
252 "_trust_env",
253 "_default_headers",
254 "_skip_auto_headers",
255 "_request_class",
256 "_response_class",
257 "_ws_response_class",
258 "_trace_configs",
259 "_read_bufsize",
260 "_max_line_size",
261 "_max_field_size",
262 "_resolve_charset",
263 "_default_proxy",
264 "_default_proxy_auth",
265 "_retry_connection",
266 "_middlewares",
267 "requote_redirect_url",
268 ]
269 )
271 _source_traceback: Optional[traceback.StackSummary] = None
272 _connector: Optional[BaseConnector] = None
274 def __init__(
275 self,
276 base_url: Optional[StrOrURL] = None,
277 *,
278 connector: Optional[BaseConnector] = None,
279 loop: Optional[asyncio.AbstractEventLoop] = None,
280 cookies: Optional[LooseCookies] = None,
281 headers: Optional[LooseHeaders] = None,
282 proxy: Optional[StrOrURL] = None,
283 proxy_auth: Optional[BasicAuth] = None,
284 skip_auto_headers: Optional[Iterable[str]] = None,
285 auth: Optional[BasicAuth] = None,
286 json_serialize: JSONEncoder = json.dumps,
287 request_class: Type[ClientRequest] = ClientRequest,
288 response_class: Type[ClientResponse] = ClientResponse,
289 ws_response_class: Type[ClientWebSocketResponse] = ClientWebSocketResponse,
290 version: HttpVersion = http.HttpVersion11,
291 cookie_jar: Optional[AbstractCookieJar] = None,
292 connector_owner: bool = True,
293 raise_for_status: Union[
294 bool, Callable[[ClientResponse], Awaitable[None]]
295 ] = False,
296 read_timeout: Union[float, _SENTINEL] = sentinel,
297 conn_timeout: Optional[float] = None,
298 timeout: Union[object, ClientTimeout] = sentinel,
299 auto_decompress: bool = True,
300 trust_env: bool = False,
301 requote_redirect_url: bool = True,
302 trace_configs: Optional[List[TraceConfig]] = None,
303 read_bufsize: int = 2**16,
304 max_line_size: int = 8190,
305 max_field_size: int = 8190,
306 fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8",
307 middlewares: Sequence[ClientMiddlewareType] = (),
308 ssl_shutdown_timeout: Union[_SENTINEL, None, float] = sentinel,
309 ) -> None:
310 # We initialise _connector to None immediately, as it's referenced in __del__()
311 # and could cause issues if an exception occurs during initialisation.
312 self._connector: Optional[BaseConnector] = None
314 if loop is None:
315 if connector is not None:
316 loop = connector._loop
318 loop = loop or asyncio.get_running_loop()
320 if base_url is None or isinstance(base_url, URL):
321 self._base_url: Optional[URL] = base_url
322 self._base_url_origin = None if base_url is None else base_url.origin()
323 else:
324 self._base_url = URL(base_url)
325 self._base_url_origin = self._base_url.origin()
326 assert self._base_url.absolute, "Only absolute URLs are supported"
327 if self._base_url is not None and not self._base_url.path.endswith("/"):
328 raise ValueError("base_url must have a trailing '/'")
330 if timeout is sentinel or timeout is None:
331 self._timeout = DEFAULT_TIMEOUT
332 if read_timeout is not sentinel:
333 warnings.warn(
334 "read_timeout is deprecated, use timeout argument instead",
335 DeprecationWarning,
336 stacklevel=2,
337 )
338 self._timeout = attr.evolve(self._timeout, total=read_timeout)
339 if conn_timeout is not None:
340 self._timeout = attr.evolve(self._timeout, connect=conn_timeout)
341 warnings.warn(
342 "conn_timeout is deprecated, use timeout argument instead",
343 DeprecationWarning,
344 stacklevel=2,
345 )
346 else:
347 if not isinstance(timeout, ClientTimeout):
348 raise ValueError(
349 f"timeout parameter cannot be of {type(timeout)} type, "
350 "please use 'timeout=ClientTimeout(...)'",
351 )
352 self._timeout = timeout
353 if read_timeout is not sentinel:
354 raise ValueError(
355 "read_timeout and timeout parameters "
356 "conflict, please setup "
357 "timeout.read"
358 )
359 if conn_timeout is not None:
360 raise ValueError(
361 "conn_timeout and timeout parameters "
362 "conflict, please setup "
363 "timeout.connect"
364 )
366 if ssl_shutdown_timeout is not sentinel:
367 warnings.warn(
368 "The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0",
369 DeprecationWarning,
370 stacklevel=2,
371 )
373 if connector is None:
374 connector = TCPConnector(
375 loop=loop, ssl_shutdown_timeout=ssl_shutdown_timeout
376 )
378 if connector._loop is not loop:
379 raise RuntimeError("Session and connector has to use same event loop")
381 self._loop = loop
383 if loop.get_debug():
384 self._source_traceback = traceback.extract_stack(sys._getframe(1))
386 if cookie_jar is None:
387 cookie_jar = CookieJar(loop=loop)
388 self._cookie_jar = cookie_jar
390 if cookies:
391 self._cookie_jar.update_cookies(cookies)
393 self._connector = connector
394 self._connector_owner = connector_owner
395 self._default_auth = auth
396 self._version = version
397 self._json_serialize = json_serialize
398 self._raise_for_status = raise_for_status
399 self._auto_decompress = auto_decompress
400 self._trust_env = trust_env
401 self._requote_redirect_url = requote_redirect_url
402 self._read_bufsize = read_bufsize
403 self._max_line_size = max_line_size
404 self._max_field_size = max_field_size
406 # Convert to list of tuples
407 if headers:
408 real_headers: CIMultiDict[str] = CIMultiDict(headers)
409 else:
410 real_headers = CIMultiDict()
411 self._default_headers: CIMultiDict[str] = real_headers
412 if skip_auto_headers is not None:
413 self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers)
414 else:
415 self._skip_auto_headers = frozenset()
417 self._request_class = request_class
418 self._response_class = response_class
419 self._ws_response_class = ws_response_class
421 self._trace_configs = trace_configs or []
422 for trace_config in self._trace_configs:
423 trace_config.freeze()
425 self._resolve_charset = fallback_charset_resolver
427 self._default_proxy = proxy
428 self._default_proxy_auth = proxy_auth
429 self._retry_connection: bool = True
430 self._middlewares = middlewares
432 def __init_subclass__(cls: Type["ClientSession"]) -> None:
433 warnings.warn(
434 "Inheritance class {} from ClientSession "
435 "is discouraged".format(cls.__name__),
436 DeprecationWarning,
437 stacklevel=2,
438 )
440 if DEBUG:
442 def __setattr__(self, name: str, val: Any) -> None:
443 if name not in self.ATTRS:
444 warnings.warn(
445 "Setting custom ClientSession.{} attribute "
446 "is discouraged".format(name),
447 DeprecationWarning,
448 stacklevel=2,
449 )
450 super().__setattr__(name, val)
452 def __del__(self, _warnings: Any = warnings) -> None:
453 if not self.closed:
454 kwargs = {"source": self}
455 _warnings.warn(
456 f"Unclosed client session {self!r}", ResourceWarning, **kwargs
457 )
458 context = {"client_session": self, "message": "Unclosed client session"}
459 if self._source_traceback is not None:
460 context["source_traceback"] = self._source_traceback
461 self._loop.call_exception_handler(context)
463 if sys.version_info >= (3, 11) and TYPE_CHECKING:
465 def request(
466 self,
467 method: str,
468 url: StrOrURL,
469 **kwargs: Unpack[_RequestOptions],
470 ) -> "_RequestContextManager": ...
472 else:
474 def request(
475 self, method: str, url: StrOrURL, **kwargs: Any
476 ) -> "_RequestContextManager":
477 """Perform HTTP request."""
478 return _RequestContextManager(self._request(method, url, **kwargs))
480 def _build_url(self, str_or_url: StrOrURL) -> URL:
481 url = URL(str_or_url)
482 if self._base_url and not url.absolute:
483 return self._base_url.join(url)
484 return url
486 async def _request(
487 self,
488 method: str,
489 str_or_url: StrOrURL,
490 *,
491 params: Query = None,
492 data: Any = None,
493 json: Any = None,
494 cookies: Optional[LooseCookies] = None,
495 headers: Optional[LooseHeaders] = None,
496 skip_auto_headers: Optional[Iterable[str]] = None,
497 auth: Optional[BasicAuth] = None,
498 allow_redirects: bool = True,
499 max_redirects: int = 10,
500 compress: Union[str, bool, None] = None,
501 chunked: Optional[bool] = None,
502 expect100: bool = False,
503 raise_for_status: Union[
504 None, bool, Callable[[ClientResponse], Awaitable[None]]
505 ] = None,
506 read_until_eof: bool = True,
507 proxy: Optional[StrOrURL] = None,
508 proxy_auth: Optional[BasicAuth] = None,
509 timeout: Union[ClientTimeout, _SENTINEL] = sentinel,
510 verify_ssl: Optional[bool] = None,
511 fingerprint: Optional[bytes] = None,
512 ssl_context: Optional[SSLContext] = None,
513 ssl: Union[SSLContext, bool, Fingerprint] = True,
514 server_hostname: Optional[str] = None,
515 proxy_headers: Optional[LooseHeaders] = None,
516 trace_request_ctx: Optional[Mapping[str, Any]] = None,
517 read_bufsize: Optional[int] = None,
518 auto_decompress: Optional[bool] = None,
519 max_line_size: Optional[int] = None,
520 max_field_size: Optional[int] = None,
521 middlewares: Optional[Sequence[ClientMiddlewareType]] = None,
522 ) -> ClientResponse:
524 # NOTE: timeout clamps existing connect and read timeouts. We cannot
525 # set the default to None because we need to detect if the user wants
526 # to use the existing timeouts by setting timeout to None.
528 if self.closed:
529 raise RuntimeError("Session is closed")
531 ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
533 if data is not None and json is not None:
534 raise ValueError(
535 "data and json parameters can not be used at the same time"
536 )
537 elif json is not None:
538 data = payload.JsonPayload(json, dumps=self._json_serialize)
540 if not isinstance(chunked, bool) and chunked is not None:
541 warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
543 redirects = 0
544 history: List[ClientResponse] = []
545 version = self._version
546 params = params or {}
548 # Merge with default headers and transform to CIMultiDict
549 headers = self._prepare_headers(headers)
551 try:
552 url = self._build_url(str_or_url)
553 except ValueError as e:
554 raise InvalidUrlClientError(str_or_url) from e
556 assert self._connector is not None
557 if url.scheme not in self._connector.allowed_protocol_schema_set:
558 raise NonHttpUrlClientError(url)
560 skip_headers: Optional[Iterable[istr]]
561 if skip_auto_headers is not None:
562 skip_headers = {
563 istr(i) for i in skip_auto_headers
564 } | self._skip_auto_headers
565 elif self._skip_auto_headers:
566 skip_headers = self._skip_auto_headers
567 else:
568 skip_headers = None
570 if proxy is None:
571 proxy = self._default_proxy
572 if proxy_auth is None:
573 proxy_auth = self._default_proxy_auth
575 if proxy is None:
576 proxy_headers = None
577 else:
578 proxy_headers = self._prepare_headers(proxy_headers)
579 try:
580 proxy = URL(proxy)
581 except ValueError as e:
582 raise InvalidURL(proxy) from e
584 if timeout is sentinel:
585 real_timeout: ClientTimeout = self._timeout
586 else:
587 if not isinstance(timeout, ClientTimeout):
588 real_timeout = ClientTimeout(total=timeout)
589 else:
590 real_timeout = timeout
591 # timeout is cumulative for all request operations
592 # (request, redirects, responses, data consuming)
593 tm = TimeoutHandle(
594 self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold
595 )
596 handle = tm.start()
598 if read_bufsize is None:
599 read_bufsize = self._read_bufsize
601 if auto_decompress is None:
602 auto_decompress = self._auto_decompress
604 if max_line_size is None:
605 max_line_size = self._max_line_size
607 if max_field_size is None:
608 max_field_size = self._max_field_size
610 traces = [
611 Trace(
612 self,
613 trace_config,
614 trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
615 )
616 for trace_config in self._trace_configs
617 ]
619 for trace in traces:
620 await trace.send_request_start(method, url.update_query(params), headers)
622 timer = tm.timer()
623 try:
624 with timer:
625 # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests
626 retry_persistent_connection = (
627 self._retry_connection and method in IDEMPOTENT_METHODS
628 )
629 while True:
630 url, auth_from_url = strip_auth_from_url(url)
631 if not url.raw_host:
632 # NOTE: Bail early, otherwise, causes `InvalidURL` through
633 # NOTE: `self._request_class()` below.
634 err_exc_cls = (
635 InvalidUrlRedirectClientError
636 if redirects
637 else InvalidUrlClientError
638 )
639 raise err_exc_cls(url)
640 # If `auth` was passed for an already authenticated URL,
641 # disallow only if this is the initial URL; this is to avoid issues
642 # with sketchy redirects that are not the caller's responsibility
643 if not history and (auth and auth_from_url):
644 raise ValueError(
645 "Cannot combine AUTH argument with "
646 "credentials encoded in URL"
647 )
649 # Override the auth with the one from the URL only if we
650 # have no auth, or if we got an auth from a redirect URL
651 if auth is None or (history and auth_from_url is not None):
652 auth = auth_from_url
654 if (
655 auth is None
656 and self._default_auth
657 and (
658 not self._base_url or self._base_url_origin == url.origin()
659 )
660 ):
661 auth = self._default_auth
663 # Try netrc if auth is still None and trust_env is enabled.
664 # Only check if NETRC environment variable is set to avoid
665 # creating an expensive executor job unnecessarily.
666 if (
667 auth is None
668 and self._trust_env
669 and url.host is not None
670 and os.environ.get("NETRC")
671 ):
672 auth = await self._loop.run_in_executor(
673 None, self._get_netrc_auth, url.host
674 )
676 # It would be confusing if we support explicit
677 # Authorization header with auth argument
678 if (
679 headers is not None
680 and auth is not None
681 and hdrs.AUTHORIZATION in headers
682 ):
683 raise ValueError(
684 "Cannot combine AUTHORIZATION header "
685 "with AUTH argument or credentials "
686 "encoded in URL"
687 )
689 all_cookies = self._cookie_jar.filter_cookies(url)
691 if cookies is not None:
692 tmp_cookie_jar = CookieJar(
693 quote_cookie=self._cookie_jar.quote_cookie
694 )
695 tmp_cookie_jar.update_cookies(cookies)
696 req_cookies = tmp_cookie_jar.filter_cookies(url)
697 if req_cookies:
698 all_cookies.load(req_cookies)
700 proxy_: Optional[URL] = None
701 if proxy is not None:
702 proxy_ = URL(proxy)
703 elif self._trust_env:
704 with suppress(LookupError):
705 proxy_, proxy_auth = await asyncio.to_thread(
706 get_env_proxy_for_url, url
707 )
709 req = self._request_class(
710 method,
711 url,
712 params=params,
713 headers=headers,
714 skip_auto_headers=skip_headers,
715 data=data,
716 cookies=all_cookies,
717 auth=auth,
718 version=version,
719 compress=compress,
720 chunked=chunked,
721 expect100=expect100,
722 loop=self._loop,
723 response_class=self._response_class,
724 proxy=proxy_,
725 proxy_auth=proxy_auth,
726 timer=timer,
727 session=self,
728 ssl=ssl if ssl is not None else True,
729 server_hostname=server_hostname,
730 proxy_headers=proxy_headers,
731 traces=traces,
732 trust_env=self.trust_env,
733 )
735 async def _connect_and_send_request(
736 req: ClientRequest,
737 ) -> ClientResponse:
738 # connection timeout
739 assert self._connector is not None
740 try:
741 conn = await self._connector.connect(
742 req, traces=traces, timeout=real_timeout
743 )
744 except asyncio.TimeoutError as exc:
745 raise ConnectionTimeoutError(
746 f"Connection timeout to host {req.url}"
747 ) from exc
749 assert conn.protocol is not None
750 conn.protocol.set_response_params(
751 timer=timer,
752 skip_payload=req.method in EMPTY_BODY_METHODS,
753 read_until_eof=read_until_eof,
754 auto_decompress=auto_decompress,
755 read_timeout=real_timeout.sock_read,
756 read_bufsize=read_bufsize,
757 timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
758 max_line_size=max_line_size,
759 max_field_size=max_field_size,
760 )
761 try:
762 resp = await req.send(conn)
763 try:
764 await resp.start(conn)
765 except BaseException:
766 resp.close()
767 raise
768 except BaseException:
769 conn.close()
770 raise
771 return resp
773 # Apply middleware (if any) - per-request middleware overrides session middleware
774 effective_middlewares = (
775 self._middlewares if middlewares is None else middlewares
776 )
778 if effective_middlewares:
779 handler = build_client_middlewares(
780 _connect_and_send_request, effective_middlewares
781 )
782 else:
783 handler = _connect_and_send_request
785 try:
786 resp = await handler(req)
787 # Client connector errors should not be retried
788 except (
789 ConnectionTimeoutError,
790 ClientConnectorError,
791 ClientConnectorCertificateError,
792 ClientConnectorSSLError,
793 ):
794 raise
795 except (ClientOSError, ServerDisconnectedError):
796 if retry_persistent_connection:
797 retry_persistent_connection = False
798 continue
799 raise
800 except ClientError:
801 raise
802 except OSError as exc:
803 if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
804 raise
805 raise ClientOSError(*exc.args) from exc
807 # Update cookies from raw headers to preserve duplicates
808 if resp._raw_cookie_headers:
809 self._cookie_jar.update_cookies_from_headers(
810 resp._raw_cookie_headers, resp.url
811 )
813 # redirects
814 if resp.status in (301, 302, 303, 307, 308) and allow_redirects:
816 for trace in traces:
817 await trace.send_request_redirect(
818 method, url.update_query(params), headers, resp
819 )
821 redirects += 1
822 history.append(resp)
823 if max_redirects and redirects >= max_redirects:
824 if req._body is not None:
825 await req._body.close()
826 resp.close()
827 raise TooManyRedirects(
828 history[0].request_info, tuple(history)
829 )
831 # For 301 and 302, mimic IE, now changed in RFC
832 # https://github.com/kennethreitz/requests/pull/269
833 if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (
834 resp.status in (301, 302) and resp.method == hdrs.METH_POST
835 ):
836 method = hdrs.METH_GET
837 data = None
838 if headers.get(hdrs.CONTENT_LENGTH):
839 headers.pop(hdrs.CONTENT_LENGTH)
840 else:
841 # For 307/308, always preserve the request body
842 # For 301/302 with non-POST methods, preserve the request body
843 # https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1
844 # Use the existing payload to avoid recreating it from a potentially consumed file
845 data = req._body
847 r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
848 hdrs.URI
849 )
850 if r_url is None:
851 # see github.com/aio-libs/aiohttp/issues/2022
852 break
853 else:
854 # reading from correct redirection
855 # response is forbidden
856 resp.release()
858 try:
859 parsed_redirect_url = URL(
860 r_url, encoded=not self._requote_redirect_url
861 )
862 except ValueError as e:
863 if req._body is not None:
864 await req._body.close()
865 resp.close()
866 raise InvalidUrlRedirectClientError(
867 r_url,
868 "Server attempted redirecting to a location that does not look like a URL",
869 ) from e
871 scheme = parsed_redirect_url.scheme
872 if scheme not in HTTP_AND_EMPTY_SCHEMA_SET:
873 if req._body is not None:
874 await req._body.close()
875 resp.close()
876 raise NonHttpUrlRedirectClientError(r_url)
877 elif not scheme:
878 parsed_redirect_url = url.join(parsed_redirect_url)
880 try:
881 redirect_origin = parsed_redirect_url.origin()
882 except ValueError as origin_val_err:
883 if req._body is not None:
884 await req._body.close()
885 resp.close()
886 raise InvalidUrlRedirectClientError(
887 parsed_redirect_url,
888 "Invalid redirect URL origin",
889 ) from origin_val_err
891 if url.origin() != redirect_origin:
892 auth = None
893 headers.pop(hdrs.AUTHORIZATION, None)
895 url = parsed_redirect_url
896 params = {}
897 resp.release()
898 continue
900 break
902 if req._body is not None:
903 await req._body.close()
904 # check response status
905 if raise_for_status is None:
906 raise_for_status = self._raise_for_status
908 if raise_for_status is None:
909 pass
910 elif callable(raise_for_status):
911 await raise_for_status(resp)
912 elif raise_for_status:
913 resp.raise_for_status()
915 # register connection
916 if handle is not None:
917 if resp.connection is not None:
918 resp.connection.add_callback(handle.cancel)
919 else:
920 handle.cancel()
922 resp._history = tuple(history)
924 for trace in traces:
925 await trace.send_request_end(
926 method, url.update_query(params), headers, resp
927 )
928 return resp
930 except BaseException as e:
931 # cleanup timer
932 tm.close()
933 if handle:
934 handle.cancel()
935 handle = None
937 for trace in traces:
938 await trace.send_request_exception(
939 method, url.update_query(params), headers, e
940 )
941 raise
943 def ws_connect(
944 self,
945 url: StrOrURL,
946 *,
947 method: str = hdrs.METH_GET,
948 protocols: Iterable[str] = (),
949 timeout: Union[ClientWSTimeout, _SENTINEL] = sentinel,
950 receive_timeout: Optional[float] = None,
951 autoclose: bool = True,
952 autoping: bool = True,
953 heartbeat: Optional[float] = None,
954 auth: Optional[BasicAuth] = None,
955 origin: Optional[str] = None,
956 params: Query = None,
957 headers: Optional[LooseHeaders] = None,
958 proxy: Optional[StrOrURL] = None,
959 proxy_auth: Optional[BasicAuth] = None,
960 ssl: Union[SSLContext, bool, Fingerprint] = True,
961 verify_ssl: Optional[bool] = None,
962 fingerprint: Optional[bytes] = None,
963 ssl_context: Optional[SSLContext] = None,
964 server_hostname: Optional[str] = None,
965 proxy_headers: Optional[LooseHeaders] = None,
966 compress: int = 0,
967 max_msg_size: int = 4 * 1024 * 1024,
968 ) -> "_WSRequestContextManager":
969 """Initiate websocket connection."""
970 return _WSRequestContextManager(
971 self._ws_connect(
972 url,
973 method=method,
974 protocols=protocols,
975 timeout=timeout,
976 receive_timeout=receive_timeout,
977 autoclose=autoclose,
978 autoping=autoping,
979 heartbeat=heartbeat,
980 auth=auth,
981 origin=origin,
982 params=params,
983 headers=headers,
984 proxy=proxy,
985 proxy_auth=proxy_auth,
986 ssl=ssl,
987 verify_ssl=verify_ssl,
988 fingerprint=fingerprint,
989 ssl_context=ssl_context,
990 server_hostname=server_hostname,
991 proxy_headers=proxy_headers,
992 compress=compress,
993 max_msg_size=max_msg_size,
994 )
995 )
997 async def _ws_connect(
998 self,
999 url: StrOrURL,
1000 *,
1001 method: str = hdrs.METH_GET,
1002 protocols: Iterable[str] = (),
1003 timeout: Union[ClientWSTimeout, _SENTINEL] = sentinel,
1004 receive_timeout: Optional[float] = None,
1005 autoclose: bool = True,
1006 autoping: bool = True,
1007 heartbeat: Optional[float] = None,
1008 auth: Optional[BasicAuth] = None,
1009 origin: Optional[str] = None,
1010 params: Query = None,
1011 headers: Optional[LooseHeaders] = None,
1012 proxy: Optional[StrOrURL] = None,
1013 proxy_auth: Optional[BasicAuth] = None,
1014 ssl: Union[SSLContext, bool, Fingerprint] = True,
1015 verify_ssl: Optional[bool] = None,
1016 fingerprint: Optional[bytes] = None,
1017 ssl_context: Optional[SSLContext] = None,
1018 server_hostname: Optional[str] = None,
1019 proxy_headers: Optional[LooseHeaders] = None,
1020 compress: int = 0,
1021 max_msg_size: int = 4 * 1024 * 1024,
1022 ) -> ClientWebSocketResponse:
1023 if timeout is not sentinel:
1024 if isinstance(timeout, ClientWSTimeout):
1025 ws_timeout = timeout
1026 else:
1027 warnings.warn(
1028 "parameter 'timeout' of type 'float' "
1029 "is deprecated, please use "
1030 "'timeout=ClientWSTimeout(ws_close=...)'",
1031 DeprecationWarning,
1032 stacklevel=2,
1033 )
1034 ws_timeout = ClientWSTimeout(ws_close=timeout)
1035 else:
1036 ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT
1037 if receive_timeout is not None:
1038 warnings.warn(
1039 "float parameter 'receive_timeout' "
1040 "is deprecated, please use parameter "
1041 "'timeout=ClientWSTimeout(ws_receive=...)'",
1042 DeprecationWarning,
1043 stacklevel=2,
1044 )
1045 ws_timeout = attr.evolve(ws_timeout, ws_receive=receive_timeout)
1047 if headers is None:
1048 real_headers: CIMultiDict[str] = CIMultiDict()
1049 else:
1050 real_headers = CIMultiDict(headers)
1052 default_headers = {
1053 hdrs.UPGRADE: "websocket",
1054 hdrs.CONNECTION: "Upgrade",
1055 hdrs.SEC_WEBSOCKET_VERSION: "13",
1056 }
1058 for key, value in default_headers.items():
1059 real_headers.setdefault(key, value)
1061 sec_key = base64.b64encode(os.urandom(16))
1062 real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()
1064 if protocols:
1065 real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols)
1066 if origin is not None:
1067 real_headers[hdrs.ORIGIN] = origin
1068 if compress:
1069 extstr = ws_ext_gen(compress=compress)
1070 real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr
1072 # For the sake of backward compatibility, if user passes in None, convert it to True
1073 if ssl is None:
1074 warnings.warn(
1075 "ssl=None is deprecated, please use ssl=True",
1076 DeprecationWarning,
1077 stacklevel=2,
1078 )
1079 ssl = True
1080 ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
1082 # send request
1083 resp = await self.request(
1084 method,
1085 url,
1086 params=params,
1087 headers=real_headers,
1088 read_until_eof=False,
1089 auth=auth,
1090 proxy=proxy,
1091 proxy_auth=proxy_auth,
1092 ssl=ssl,
1093 server_hostname=server_hostname,
1094 proxy_headers=proxy_headers,
1095 )
1097 try:
1098 # check handshake
1099 if resp.status != 101:
1100 raise WSServerHandshakeError(
1101 resp.request_info,
1102 resp.history,
1103 message="Invalid response status",
1104 status=resp.status,
1105 headers=resp.headers,
1106 )
1108 if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket":
1109 raise WSServerHandshakeError(
1110 resp.request_info,
1111 resp.history,
1112 message="Invalid upgrade header",
1113 status=resp.status,
1114 headers=resp.headers,
1115 )
1117 if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade":
1118 raise WSServerHandshakeError(
1119 resp.request_info,
1120 resp.history,
1121 message="Invalid connection header",
1122 status=resp.status,
1123 headers=resp.headers,
1124 )
1126 # key calculation
1127 r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")
1128 match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
1129 if r_key != match:
1130 raise WSServerHandshakeError(
1131 resp.request_info,
1132 resp.history,
1133 message="Invalid challenge response",
1134 status=resp.status,
1135 headers=resp.headers,
1136 )
1138 # websocket protocol
1139 protocol = None
1140 if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:
1141 resp_protocols = [
1142 proto.strip()
1143 for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
1144 ]
1146 for proto in resp_protocols:
1147 if proto in protocols:
1148 protocol = proto
1149 break
1151 # websocket compress
1152 notakeover = False
1153 if compress:
1154 compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)
1155 if compress_hdrs:
1156 try:
1157 compress, notakeover = ws_ext_parse(compress_hdrs)
1158 except WSHandshakeError as exc:
1159 raise WSServerHandshakeError(
1160 resp.request_info,
1161 resp.history,
1162 message=exc.args[0],
1163 status=resp.status,
1164 headers=resp.headers,
1165 ) from exc
1166 else:
1167 compress = 0
1168 notakeover = False
1170 conn = resp.connection
1171 assert conn is not None
1172 conn_proto = conn.protocol
1173 assert conn_proto is not None
1175 # For WS connection the read_timeout must be either receive_timeout or greater
1176 # None == no timeout, i.e. infinite timeout, so None is the max timeout possible
1177 if ws_timeout.ws_receive is None:
1178 # Reset regardless
1179 conn_proto.read_timeout = None
1180 elif conn_proto.read_timeout is not None:
1181 conn_proto.read_timeout = max(
1182 ws_timeout.ws_receive, conn_proto.read_timeout
1183 )
1185 transport = conn.transport
1186 assert transport is not None
1187 reader = WebSocketDataQueue(conn_proto, 2**16, loop=self._loop)
1188 conn_proto.set_parser(WebSocketReader(reader, max_msg_size), reader)
1189 writer = WebSocketWriter(
1190 conn_proto,
1191 transport,
1192 use_mask=True,
1193 compress=compress,
1194 notakeover=notakeover,
1195 )
1196 except BaseException:
1197 resp.close()
1198 raise
1199 else:
1200 return self._ws_response_class(
1201 reader,
1202 writer,
1203 protocol,
1204 resp,
1205 ws_timeout,
1206 autoclose,
1207 autoping,
1208 self._loop,
1209 heartbeat=heartbeat,
1210 compress=compress,
1211 client_notakeover=notakeover,
1212 )
1214 def _prepare_headers(self, headers: Optional[LooseHeaders]) -> "CIMultiDict[str]":
1215 """Add default headers and transform it to CIMultiDict"""
1216 # Convert headers to MultiDict
1217 result = CIMultiDict(self._default_headers)
1218 if headers:
1219 if not isinstance(headers, (MultiDictProxy, MultiDict)):
1220 headers = CIMultiDict(headers)
1221 added_names: Set[str] = set()
1222 for key, value in headers.items():
1223 if key in added_names:
1224 result.add(key, value)
1225 else:
1226 result[key] = value
1227 added_names.add(key)
1228 return result
1230 def _get_netrc_auth(self, host: str) -> Optional[BasicAuth]:
1231 """
1232 Get auth from netrc for the given host.
1234 This method is designed to be called in an executor to avoid
1235 blocking I/O in the event loop.
1236 """
1237 netrc_obj = netrc_from_env()
1238 try:
1239 return basicauth_from_netrc(netrc_obj, host)
1240 except LookupError:
1241 return None
1243 if sys.version_info >= (3, 11) and TYPE_CHECKING:
1245 def get(
1246 self,
1247 url: StrOrURL,
1248 **kwargs: Unpack[_RequestOptions],
1249 ) -> "_RequestContextManager": ...
1251 def options(
1252 self,
1253 url: StrOrURL,
1254 **kwargs: Unpack[_RequestOptions],
1255 ) -> "_RequestContextManager": ...
1257 def head(
1258 self,
1259 url: StrOrURL,
1260 **kwargs: Unpack[_RequestOptions],
1261 ) -> "_RequestContextManager": ...
1263 def post(
1264 self,
1265 url: StrOrURL,
1266 **kwargs: Unpack[_RequestOptions],
1267 ) -> "_RequestContextManager": ...
1269 def put(
1270 self,
1271 url: StrOrURL,
1272 **kwargs: Unpack[_RequestOptions],
1273 ) -> "_RequestContextManager": ...
1275 def patch(
1276 self,
1277 url: StrOrURL,
1278 **kwargs: Unpack[_RequestOptions],
1279 ) -> "_RequestContextManager": ...
1281 def delete(
1282 self,
1283 url: StrOrURL,
1284 **kwargs: Unpack[_RequestOptions],
1285 ) -> "_RequestContextManager": ...
1287 else:
1289 def get(
1290 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
1291 ) -> "_RequestContextManager":
1292 """Perform HTTP GET request."""
1293 return _RequestContextManager(
1294 self._request(
1295 hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs
1296 )
1297 )
1299 def options(
1300 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
1301 ) -> "_RequestContextManager":
1302 """Perform HTTP OPTIONS request."""
1303 return _RequestContextManager(
1304 self._request(
1305 hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs
1306 )
1307 )
1309 def head(
1310 self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any
1311 ) -> "_RequestContextManager":
1312 """Perform HTTP HEAD request."""
1313 return _RequestContextManager(
1314 self._request(
1315 hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs
1316 )
1317 )
1319 def post(
1320 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1321 ) -> "_RequestContextManager":
1322 """Perform HTTP POST request."""
1323 return _RequestContextManager(
1324 self._request(hdrs.METH_POST, url, data=data, **kwargs)
1325 )
1327 def put(
1328 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1329 ) -> "_RequestContextManager":
1330 """Perform HTTP PUT request."""
1331 return _RequestContextManager(
1332 self._request(hdrs.METH_PUT, url, data=data, **kwargs)
1333 )
1335 def patch(
1336 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1337 ) -> "_RequestContextManager":
1338 """Perform HTTP PATCH request."""
1339 return _RequestContextManager(
1340 self._request(hdrs.METH_PATCH, url, data=data, **kwargs)
1341 )
1343 def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager":
1344 """Perform HTTP DELETE request."""
1345 return _RequestContextManager(
1346 self._request(hdrs.METH_DELETE, url, **kwargs)
1347 )
1349 async def close(self) -> None:
1350 """Close underlying connector.
1352 Release all acquired resources.
1353 """
1354 if not self.closed:
1355 if self._connector is not None and self._connector_owner:
1356 await self._connector.close()
1357 self._connector = None
1359 @property
1360 def closed(self) -> bool:
1361 """Is client session closed.
1363 A readonly property.
1364 """
1365 return self._connector is None or self._connector.closed
1367 @property
1368 def connector(self) -> Optional[BaseConnector]:
1369 """Connector instance used for the session."""
1370 return self._connector
1372 @property
1373 def cookie_jar(self) -> AbstractCookieJar:
1374 """The session cookies."""
1375 return self._cookie_jar
1377 @property
1378 def version(self) -> Tuple[int, int]:
1379 """The session HTTP protocol version."""
1380 return self._version
1382 @property
1383 def requote_redirect_url(self) -> bool:
1384 """Do URL requoting on redirection handling."""
1385 return self._requote_redirect_url
1387 @requote_redirect_url.setter
1388 def requote_redirect_url(self, val: bool) -> None:
1389 """Do URL requoting on redirection handling."""
1390 warnings.warn(
1391 "session.requote_redirect_url modification is deprecated #2778",
1392 DeprecationWarning,
1393 stacklevel=2,
1394 )
1395 self._requote_redirect_url = val
1397 @property
1398 def loop(self) -> asyncio.AbstractEventLoop:
1399 """Session's loop."""
1400 warnings.warn(
1401 "client.loop property is deprecated", DeprecationWarning, stacklevel=2
1402 )
1403 return self._loop
1405 @property
1406 def timeout(self) -> ClientTimeout:
1407 """Timeout for the session."""
1408 return self._timeout
1410 @property
1411 def headers(self) -> "CIMultiDict[str]":
1412 """The default headers of the client session."""
1413 return self._default_headers
1415 @property
1416 def skip_auto_headers(self) -> FrozenSet[istr]:
1417 """Headers for which autogeneration should be skipped"""
1418 return self._skip_auto_headers
1420 @property
1421 def auth(self) -> Optional[BasicAuth]:
1422 """An object that represents HTTP Basic Authorization"""
1423 return self._default_auth
1425 @property
1426 def json_serialize(self) -> JSONEncoder:
1427 """Json serializer callable"""
1428 return self._json_serialize
1430 @property
1431 def connector_owner(self) -> bool:
1432 """Should connector be closed on session closing"""
1433 return self._connector_owner
1435 @property
1436 def raise_for_status(
1437 self,
1438 ) -> Union[bool, Callable[[ClientResponse], Awaitable[None]]]:
1439 """Should `ClientResponse.raise_for_status()` be called for each response."""
1440 return self._raise_for_status
1442 @property
1443 def auto_decompress(self) -> bool:
1444 """Should the body response be automatically decompressed."""
1445 return self._auto_decompress
1447 @property
1448 def trust_env(self) -> bool:
1449 """
1450 Should proxies information from environment or netrc be trusted.
1452 Information is from HTTP_PROXY / HTTPS_PROXY environment variables
1453 or ~/.netrc file if present.
1454 """
1455 return self._trust_env
1457 @property
1458 def trace_configs(self) -> List[TraceConfig]:
1459 """A list of TraceConfig instances used for client tracing"""
1460 return self._trace_configs
1462 def detach(self) -> None:
1463 """Detach connector from session without closing the former.
1465 Session is switched to closed state anyway.
1466 """
1467 self._connector = None
1469 def __enter__(self) -> None:
1470 raise TypeError("Use async with instead")
1472 def __exit__(
1473 self,
1474 exc_type: Optional[Type[BaseException]],
1475 exc_val: Optional[BaseException],
1476 exc_tb: Optional[TracebackType],
1477 ) -> None:
1478 # __exit__ should exist in pair with __enter__ but never executed
1479 pass # pragma: no cover
1481 async def __aenter__(self) -> "ClientSession":
1482 return self
1484 async def __aexit__(
1485 self,
1486 exc_type: Optional[Type[BaseException]],
1487 exc_val: Optional[BaseException],
1488 exc_tb: Optional[TracebackType],
1489 ) -> None:
1490 await self.close()
1493class _BaseRequestContextManager(Coroutine[Any, Any, _RetType], Generic[_RetType]):
1495 __slots__ = ("_coro", "_resp")
1497 def __init__(self, coro: Coroutine["asyncio.Future[Any]", None, _RetType]) -> None:
1498 self._coro: Coroutine["asyncio.Future[Any]", None, _RetType] = coro
1500 def send(self, arg: None) -> "asyncio.Future[Any]":
1501 return self._coro.send(arg)
1503 def throw(self, *args: Any, **kwargs: Any) -> "asyncio.Future[Any]":
1504 return self._coro.throw(*args, **kwargs)
1506 def close(self) -> None:
1507 return self._coro.close()
1509 def __await__(self) -> Generator[Any, None, _RetType]:
1510 ret = self._coro.__await__()
1511 return ret
1513 def __iter__(self) -> Generator[Any, None, _RetType]:
1514 return self.__await__()
1516 async def __aenter__(self) -> _RetType:
1517 self._resp: _RetType = await self._coro
1518 return await self._resp.__aenter__()
1520 async def __aexit__(
1521 self,
1522 exc_type: Optional[Type[BaseException]],
1523 exc: Optional[BaseException],
1524 tb: Optional[TracebackType],
1525 ) -> None:
1526 await self._resp.__aexit__(exc_type, exc, tb)
1529_RequestContextManager = _BaseRequestContextManager[ClientResponse]
1530_WSRequestContextManager = _BaseRequestContextManager[ClientWebSocketResponse]
1533class _SessionRequestContextManager:
1535 __slots__ = ("_coro", "_resp", "_session")
1537 def __init__(
1538 self,
1539 coro: Coroutine["asyncio.Future[Any]", None, ClientResponse],
1540 session: ClientSession,
1541 ) -> None:
1542 self._coro = coro
1543 self._resp: Optional[ClientResponse] = None
1544 self._session = session
1546 async def __aenter__(self) -> ClientResponse:
1547 try:
1548 self._resp = await self._coro
1549 except BaseException:
1550 await self._session.close()
1551 raise
1552 else:
1553 return self._resp
1555 async def __aexit__(
1556 self,
1557 exc_type: Optional[Type[BaseException]],
1558 exc: Optional[BaseException],
1559 tb: Optional[TracebackType],
1560 ) -> None:
1561 assert self._resp is not None
1562 self._resp.close()
1563 await self._session.close()
1566if sys.version_info >= (3, 11) and TYPE_CHECKING:
1568 def request(
1569 method: str,
1570 url: StrOrURL,
1571 *,
1572 version: HttpVersion = http.HttpVersion11,
1573 connector: Optional[BaseConnector] = None,
1574 loop: Optional[asyncio.AbstractEventLoop] = None,
1575 **kwargs: Unpack[_RequestOptions],
1576 ) -> _SessionRequestContextManager: ...
1578else:
1580 def request(
1581 method: str,
1582 url: StrOrURL,
1583 *,
1584 version: HttpVersion = http.HttpVersion11,
1585 connector: Optional[BaseConnector] = None,
1586 loop: Optional[asyncio.AbstractEventLoop] = None,
1587 **kwargs: Any,
1588 ) -> _SessionRequestContextManager:
1589 """Constructs and sends a request.
1591 Returns response object.
1592 method - HTTP method
1593 url - request url
1594 params - (optional) Dictionary or bytes to be sent in the query
1595 string of the new request
1596 data - (optional) Dictionary, bytes, or file-like object to
1597 send in the body of the request
1598 json - (optional) Any json compatible python object
1599 headers - (optional) Dictionary of HTTP Headers to send with
1600 the request
1601 cookies - (optional) Dict object to send with the request
1602 auth - (optional) BasicAuth named tuple represent HTTP Basic Auth
1603 auth - aiohttp.helpers.BasicAuth
1604 allow_redirects - (optional) If set to False, do not follow
1605 redirects
1606 version - Request HTTP version.
1607 compress - Set to True if request has to be compressed
1608 with deflate encoding.
1609 chunked - Set to chunk size for chunked transfer encoding.
1610 expect100 - Expect 100-continue response from server.
1611 connector - BaseConnector sub-class instance to support
1612 connection pooling.
1613 read_until_eof - Read response until eof if response
1614 does not have Content-Length header.
1615 loop - Optional event loop.
1616 timeout - Optional ClientTimeout settings structure, 5min
1617 total timeout by default.
1618 Usage::
1619 >>> import aiohttp
1620 >>> async with aiohttp.request('GET', 'http://python.org/') as resp:
1621 ... print(resp)
1622 ... data = await resp.read()
1623 <ClientResponse(https://www.python.org/) [200 OK]>
1624 """
1625 connector_owner = False
1626 if connector is None:
1627 connector_owner = True
1628 connector = TCPConnector(loop=loop, force_close=True)
1630 session = ClientSession(
1631 loop=loop,
1632 cookies=kwargs.pop("cookies", None),
1633 version=version,
1634 timeout=kwargs.pop("timeout", sentinel),
1635 connector=connector,
1636 connector_owner=connector_owner,
1637 )
1639 return _SessionRequestContextManager(
1640 session._request(method, url, **kwargs),
1641 session,
1642 )