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