Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/aiohttp/client.py: 56%
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 UploadAbortedError,
68 WSMessageTypeError,
69 WSServerHandshakeError,
70)
71from .client_middlewares import ClientMiddlewareType, build_client_middlewares
72from .client_reqrep import (
73 ClientRequest as ClientRequest,
74 ClientResponse as ClientResponse,
75 Fingerprint as Fingerprint,
76 RequestInfo as RequestInfo,
77 UploadTracker,
78 _merge_ssl_params,
79)
80from .client_ws import (
81 DEFAULT_WS_CLIENT_TIMEOUT,
82 ClientWebSocketResponse as ClientWebSocketResponse,
83 ClientWSTimeout as ClientWSTimeout,
84)
85from .connector import (
86 HTTP_AND_EMPTY_SCHEMA_SET,
87 BaseConnector as BaseConnector,
88 NamedPipeConnector as NamedPipeConnector,
89 TCPConnector as TCPConnector,
90 UnixConnector as UnixConnector,
91)
92from .cookiejar import CookieJar, _UnlimitedCookieJar
93from .helpers import (
94 _SENTINEL,
95 DEBUG,
96 DEFAULT_CHUNK_SIZE,
97 EMPTY_BODY_METHODS,
98 BasicAuth,
99 TimeoutHandle,
100 basicauth_from_netrc,
101 get_env_proxy_for_url,
102 netrc_from_env,
103 sentinel,
104 strip_auth_from_url,
105)
106from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter
107from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse
108from .tracing import Trace, TraceConfig
109from .typedefs import (
110 JSONBytesEncoder,
111 JSONEncoder,
112 LooseCookies,
113 LooseHeaders,
114 Query,
115 StrOrURL,
116)
118__all__ = (
119 # client_exceptions
120 "ClientConnectionError",
121 "ClientConnectionResetError",
122 "ClientConnectorCertificateError",
123 "ClientConnectorDNSError",
124 "ClientConnectorError",
125 "ClientConnectorSSLError",
126 "ClientError",
127 "ClientHttpProxyError",
128 "ClientOSError",
129 "ClientPayloadError",
130 "ClientProxyConnectionError",
131 "ClientResponseError",
132 "ClientSSLError",
133 "ConnectionTimeoutError",
134 "ContentTypeError",
135 "InvalidURL",
136 "InvalidUrlClientError",
137 "RedirectClientError",
138 "NonHttpUrlClientError",
139 "InvalidUrlRedirectClientError",
140 "NonHttpUrlRedirectClientError",
141 "ServerConnectionError",
142 "ServerDisconnectedError",
143 "ServerFingerprintMismatch",
144 "ServerTimeoutError",
145 "SocketTimeoutError",
146 "TooManyRedirects",
147 "UploadAbortedError",
148 "WSServerHandshakeError",
149 # client_reqrep
150 "ClientRequest",
151 "ClientResponse",
152 "Fingerprint",
153 "RequestInfo",
154 "UploadTracker",
155 # connector
156 "BaseConnector",
157 "TCPConnector",
158 "UnixConnector",
159 "NamedPipeConnector",
160 # client_ws
161 "ClientWebSocketResponse",
162 # client
163 "ClientSession",
164 "ClientTimeout",
165 "ClientWSTimeout",
166 "request",
167 "WSMessageTypeError",
168)
171if TYPE_CHECKING:
172 from ssl import SSLContext
173else:
174 SSLContext = Any
176if sys.version_info >= (3, 11) and TYPE_CHECKING:
177 from typing import Unpack
180# URL parsers strip leading and trailing C0 control characters and spaces and
181# drop tabs and newlines before splitting a URL.
182_C0_CONTROL_OR_SPACE = "".join(map(chr, range(0x21)))
183_REMOVE_TAB_OR_NEWLINE = str.maketrans("", "", "\t\n\r")
186def _has_no_authority(location: str, scheme: str) -> bool:
187 """Tell if a URL with a scheme has no "//" after the scheme.
189 Browsers read a backslash like a slash there for http and https.
190 """
191 location = location.strip(_C0_CONTROL_OR_SPACE).translate(_REMOVE_TAB_OR_NEWLINE)
192 rest = location[len(scheme) + 1 : len(scheme) + 3]
193 return rest[:1] not in ("/", "\\") or rest[1:] not in ("/", "\\")
196class _RequestOptions(TypedDict, total=False):
197 params: Query
198 data: Any
199 json: Any
200 cookies: LooseCookies | None
201 headers: LooseHeaders | None
202 skip_auto_headers: Iterable[str] | None
203 auth: BasicAuth | None
204 allow_redirects: bool
205 max_redirects: int
206 compress: str | bool | None
207 chunked: bool | None
208 expect100: bool
209 raise_for_status: None | bool | Callable[[ClientResponse], Awaitable[None]]
210 read_until_eof: bool
211 proxy: StrOrURL | None
212 proxy_auth: BasicAuth | None
213 timeout: "ClientTimeout | _SENTINEL | None"
214 ssl: SSLContext | bool | Fingerprint
215 server_hostname: str | None
216 proxy_headers: LooseHeaders | None
217 trace_request_ctx: object
218 read_bufsize: int | None
219 auto_decompress: bool | None
220 max_line_size: int | None
221 max_field_size: int | None
222 max_headers: int | None
223 middlewares: Sequence[ClientMiddlewareType] | None
224 upload_tracker: UploadTracker | None
227class _WSConnectOptions(TypedDict, total=False):
228 method: str
229 protocols: Iterable[str]
230 timeout: "ClientWSTimeout | _SENTINEL"
231 receive_timeout: float | None
232 autoclose: bool
233 autoping: bool
234 heartbeat: float | None
235 auth: BasicAuth | None
236 origin: str | None
237 params: Query
238 headers: LooseHeaders | None
239 proxy: StrOrURL | None
240 proxy_auth: BasicAuth | None
241 ssl: SSLContext | bool | Fingerprint
242 verify_ssl: bool | None
243 fingerprint: bytes | None
244 ssl_context: SSLContext | None
245 server_hostname: str | None
246 proxy_headers: LooseHeaders | None
247 compress: int
248 max_msg_size: int
251@attr.s(auto_attribs=True, frozen=True, slots=True)
252class ClientTimeout:
253 total: float | None = None
254 connect: float | None = None
255 sock_read: float | None = None
256 sock_connect: float | None = None
257 ceil_threshold: float = 5
259 # pool_queue_timeout: Optional[float] = None
260 # dns_resolution_timeout: Optional[float] = None
261 # socket_connect_timeout: Optional[float] = None
262 # connection_acquiring_timeout: Optional[float] = None
263 # new_connection_timeout: Optional[float] = None
264 # http_header_timeout: Optional[float] = None
265 # response_body_timeout: Optional[float] = None
267 # to create a timeout specific for a single request, either
268 # - create a completely new one to overwrite the default
269 # - or use http://www.attrs.org/en/stable/api.html#attr.evolve
270 # to overwrite the defaults
273# 5 Minute default read timeout
274DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60, sock_connect=30)
276# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2
277IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"})
279_RetType_co = TypeVar(
280 "_RetType_co",
281 bound="ClientResponse | ClientWebSocketResponse[bool]",
282 covariant=True,
283)
284_CharsetResolver = Callable[[ClientResponse, bytes], str]
287class ClientSession:
288 """First-class interface for making HTTP requests."""
290 ATTRS = frozenset(
291 [
292 "_base_url",
293 "_base_url_origin",
294 "_source_traceback",
295 "_connector",
296 "_loop",
297 "_cookie_jar",
298 "_connector_owner",
299 "_default_auth",
300 "_version",
301 "_json_serialize",
302 "_json_serialize_bytes",
303 "_requote_redirect_url",
304 "_timeout",
305 "_raise_for_status",
306 "_auto_decompress",
307 "_trust_env",
308 "_default_headers",
309 "_skip_auto_headers",
310 "_request_class",
311 "_response_class",
312 "_ws_response_class",
313 "_trace_configs",
314 "_read_bufsize",
315 "_max_line_size",
316 "_max_field_size",
317 "_max_headers",
318 "_resolve_charset",
319 "_default_proxy",
320 "_default_proxy_auth",
321 "_retry_connection",
322 "_middlewares",
323 "requote_redirect_url",
324 ]
325 )
327 _source_traceback: traceback.StackSummary | None = None
328 _connector: BaseConnector | None = None
330 def __init__(
331 self,
332 base_url: StrOrURL | None = None,
333 *,
334 connector: BaseConnector | None = None,
335 loop: asyncio.AbstractEventLoop | None = None,
336 cookies: LooseCookies | None = None,
337 headers: LooseHeaders | None = None,
338 proxy: StrOrURL | None = None,
339 proxy_auth: BasicAuth | None = None,
340 skip_auto_headers: Iterable[str] | None = None,
341 auth: BasicAuth | None = None,
342 json_serialize: JSONEncoder = json.dumps,
343 json_serialize_bytes: JSONBytesEncoder | None = None,
344 request_class: type[ClientRequest] = ClientRequest,
345 response_class: type[ClientResponse] = ClientResponse,
346 ws_response_class: type[ClientWebSocketResponse] = ClientWebSocketResponse,
347 version: HttpVersion = http.HttpVersion11,
348 cookie_jar: AbstractCookieJar | None = None,
349 connector_owner: bool = True,
350 raise_for_status: bool | Callable[[ClientResponse], Awaitable[None]] = False,
351 read_timeout: float | _SENTINEL = sentinel,
352 conn_timeout: float | None = None,
353 timeout: object | ClientTimeout = sentinel,
354 auto_decompress: bool = True,
355 trust_env: bool = False,
356 requote_redirect_url: bool = True,
357 trace_configs: list[TraceConfig] | None = None,
358 read_bufsize: int = DEFAULT_CHUNK_SIZE,
359 max_line_size: int = 8190,
360 max_field_size: int = 8190,
361 max_headers: int = 128,
362 fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8",
363 middlewares: Sequence[ClientMiddlewareType] = (),
364 ssl_shutdown_timeout: _SENTINEL | None | float = sentinel,
365 ) -> None:
366 # We initialise _connector to None immediately, as it's referenced in __del__()
367 # and could cause issues if an exception occurs during initialisation.
368 self._connector: BaseConnector | None = None
370 if loop is None:
371 if connector is not None:
372 loop = connector._loop
374 loop = loop or asyncio.get_running_loop()
376 if base_url is None or isinstance(base_url, URL):
377 self._base_url: URL | None = base_url
378 self._base_url_origin = None if base_url is None else base_url.origin()
379 else:
380 self._base_url = URL(base_url)
381 self._base_url_origin = self._base_url.origin()
382 assert self._base_url.absolute, "Only absolute URLs are supported"
383 if self._base_url is not None and not self._base_url.path.endswith("/"):
384 raise ValueError("base_url must have a trailing '/'")
386 if timeout is sentinel or timeout is None:
387 self._timeout = DEFAULT_TIMEOUT
388 if read_timeout is not sentinel:
389 warnings.warn(
390 "read_timeout is deprecated, use timeout argument instead",
391 DeprecationWarning,
392 stacklevel=2,
393 )
394 self._timeout = attr.evolve(self._timeout, total=read_timeout)
395 if conn_timeout is not None:
396 self._timeout = attr.evolve(self._timeout, connect=conn_timeout)
397 warnings.warn(
398 "conn_timeout is deprecated, use timeout argument instead",
399 DeprecationWarning,
400 stacklevel=2,
401 )
402 else:
403 if not isinstance(timeout, ClientTimeout):
404 raise ValueError(
405 f"timeout parameter cannot be of {type(timeout)} type, "
406 "please use 'timeout=ClientTimeout(...)'",
407 )
408 self._timeout = timeout
409 if read_timeout is not sentinel:
410 raise ValueError(
411 "read_timeout and timeout parameters "
412 "conflict, please setup "
413 "timeout.read"
414 )
415 if conn_timeout is not None:
416 raise ValueError(
417 "conn_timeout and timeout parameters "
418 "conflict, please setup "
419 "timeout.connect"
420 )
422 if ssl_shutdown_timeout is not sentinel:
423 warnings.warn(
424 "The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0",
425 DeprecationWarning,
426 stacklevel=2,
427 )
429 if connector is None:
430 connector = TCPConnector(
431 loop=loop, ssl_shutdown_timeout=ssl_shutdown_timeout
432 )
434 if connector._loop is not loop:
435 raise RuntimeError("Session and connector has to use same event loop")
437 self._loop = loop
439 if loop.get_debug():
440 self._source_traceback = traceback.extract_stack(sys._getframe(1))
442 if cookie_jar is None:
443 cookie_jar = CookieJar(loop=loop)
444 self._cookie_jar = cookie_jar
446 if cookies:
447 self._cookie_jar.update_cookies(cookies)
449 if auth is not None:
450 warnings.warn(
451 "The 'auth' parameter is deprecated and will be removed in v4;"
452 " pass headers={'Authorization': "
453 "aiohttp.encode_basic_auth(login, password)} instead",
454 DeprecationWarning,
455 stacklevel=2,
456 )
457 if proxy_auth is not None:
458 warnings.warn(
459 "The 'proxy_auth' parameter is deprecated and will be removed in v4;"
460 " pass proxy_headers={'Proxy-Authorization': "
461 "aiohttp.encode_basic_auth(login, password)} instead",
462 DeprecationWarning,
463 stacklevel=2,
464 )
465 self._connector = connector
466 self._connector_owner = connector_owner
467 self._default_auth = auth
468 self._version = version
469 self._json_serialize = json_serialize
470 self._json_serialize_bytes = json_serialize_bytes
471 self._raise_for_status = raise_for_status
472 self._auto_decompress = auto_decompress
473 self._trust_env = trust_env
474 self._requote_redirect_url = requote_redirect_url
475 self._read_bufsize = read_bufsize
476 self._max_line_size = max_line_size
477 self._max_field_size = max_field_size
478 self._max_headers = max_headers
480 # Convert to list of tuples
481 if headers:
482 real_headers: CIMultiDict[str] = CIMultiDict(headers)
483 else:
484 real_headers = CIMultiDict()
485 self._default_headers: CIMultiDict[str] = real_headers
486 if skip_auto_headers is not None:
487 self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers)
488 else:
489 self._skip_auto_headers = frozenset()
491 self._request_class = request_class
492 self._response_class = response_class
493 self._ws_response_class = ws_response_class
495 self._trace_configs = trace_configs or []
496 for trace_config in self._trace_configs:
497 trace_config.freeze()
499 self._resolve_charset = fallback_charset_resolver
501 self._default_proxy = proxy
502 self._default_proxy_auth = proxy_auth
503 self._retry_connection: bool = True
504 self._middlewares = middlewares
506 def __init_subclass__(cls: type["ClientSession"]) -> None:
507 warnings.warn(
508 f"Inheritance class {cls.__name__} from ClientSession is discouraged",
509 DeprecationWarning,
510 stacklevel=2,
511 )
513 if DEBUG:
515 def __setattr__(self, name: str, val: Any) -> None:
516 if name not in self.ATTRS:
517 warnings.warn(
518 f"Setting custom ClientSession.{name} attribute is discouraged",
519 DeprecationWarning,
520 stacklevel=2,
521 )
522 super().__setattr__(name, val)
524 def __del__(self, _warnings: Any = warnings) -> None:
525 if not self.closed:
526 kwargs = {"source": self}
527 _warnings.warn(
528 f"Unclosed client session {self!r}", ResourceWarning, **kwargs
529 )
530 context = {"client_session": self, "message": "Unclosed client session"}
531 if self._source_traceback is not None:
532 context["source_traceback"] = self._source_traceback
533 self._loop.call_exception_handler(context)
535 if sys.version_info >= (3, 11) and TYPE_CHECKING:
537 def request(
538 self,
539 method: str,
540 url: StrOrURL,
541 **kwargs: Unpack[_RequestOptions],
542 ) -> "_RequestContextManager": ...
544 else:
546 def request(
547 self, method: str, url: StrOrURL, **kwargs: Any
548 ) -> "_RequestContextManager":
549 """Perform HTTP request."""
550 return _RequestContextManager(self._request(method, url, **kwargs))
552 def _build_url(self, str_or_url: StrOrURL) -> URL:
553 url = URL(str_or_url)
554 if self._base_url and not url.absolute:
555 return self._base_url.join(url)
556 return url
558 async def _request(
559 self,
560 method: str,
561 str_or_url: StrOrURL,
562 *,
563 params: Query = None,
564 data: Any = None,
565 json: Any = None,
566 cookies: LooseCookies | None = None,
567 headers: LooseHeaders | None = None,
568 skip_auto_headers: Iterable[str] | None = None,
569 auth: BasicAuth | None = None,
570 allow_redirects: bool = True,
571 max_redirects: int = 10,
572 compress: str | bool | None = None,
573 chunked: bool | None = None,
574 expect100: bool = False,
575 raise_for_status: (
576 None | bool | Callable[[ClientResponse], Awaitable[None]]
577 ) = None,
578 read_until_eof: bool = True,
579 proxy: StrOrURL | None = None,
580 proxy_auth: BasicAuth | None = None,
581 timeout: ClientTimeout | _SENTINEL = sentinel,
582 verify_ssl: bool | None = None,
583 fingerprint: bytes | None = None,
584 ssl_context: SSLContext | None = None,
585 ssl: SSLContext | bool | Fingerprint = True,
586 server_hostname: str | None = None,
587 proxy_headers: LooseHeaders | None = None,
588 trace_request_ctx: object = None,
589 read_bufsize: int | None = None,
590 auto_decompress: bool | None = None,
591 max_line_size: int | None = None,
592 max_field_size: int | None = None,
593 max_headers: int | None = None,
594 middlewares: Sequence[ClientMiddlewareType] | None = None,
595 upload_tracker: UploadTracker | None = None,
596 ) -> ClientResponse:
598 # NOTE: timeout clamps existing connect and read timeouts. We cannot
599 # set the default to None because we need to detect if the user wants
600 # to use the existing timeouts by setting timeout to None.
602 # Bound outside the settle-guaranteeing try block: a rebind error
603 # must not settle a tracker owned by another request.
604 if upload_tracker is not None:
605 upload_tracker._bind()
607 tm: TimeoutHandle | None = None
608 handle: asyncio.TimerHandle | None = None
609 # Only traces that saw send_request_start; they must also see a terminal event.
610 traces: list[Trace] = []
611 req: ClientRequest | None = None
612 resp: ClientResponse | None = None
613 try:
614 if self.closed:
615 raise RuntimeError("Session is closed")
617 method = method.upper()
619 ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
621 if auth is not None:
622 warnings.warn(
623 "The 'auth' parameter is deprecated and will be removed in v4;"
624 " pass headers={'Authorization': "
625 "aiohttp.encode_basic_auth(login, password)} instead",
626 DeprecationWarning,
627 stacklevel=3,
628 )
629 if proxy_auth is not None:
630 warnings.warn(
631 "The 'proxy_auth' parameter is deprecated and will be removed in v4;"
632 " pass proxy_headers={'Proxy-Authorization': "
633 "aiohttp.encode_basic_auth(login, password)} instead",
634 DeprecationWarning,
635 stacklevel=3,
636 )
638 if data is not None and json is not None:
639 raise ValueError(
640 "data and json parameters can not be used at the same time"
641 )
642 elif json is not None:
643 if self._json_serialize_bytes is not None:
644 data = payload.JsonBytesPayload(
645 json, dumps=self._json_serialize_bytes
646 )
647 else:
648 data = payload.JsonPayload(json, dumps=self._json_serialize)
650 if not isinstance(chunked, bool) and chunked is not None:
651 warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
653 redirects = 0
654 history: list[ClientResponse] = []
655 version = self._version
656 params = params or {}
658 # Merge with default headers and transform to CIMultiDict
659 headers = self._prepare_headers(headers)
661 try:
662 url = self._build_url(str_or_url)
663 except ValueError as e:
664 raise InvalidUrlClientError(str_or_url) from e
666 assert self._connector is not None
667 if url.scheme not in self._connector.allowed_protocol_schema_set:
668 raise NonHttpUrlClientError(url)
670 skip_headers: Iterable[istr] | None
671 if skip_auto_headers is not None:
672 skip_headers = {
673 istr(i) for i in skip_auto_headers
674 } | self._skip_auto_headers
675 elif self._skip_auto_headers:
676 skip_headers = self._skip_auto_headers
677 else:
678 skip_headers = None
680 if proxy is None:
681 proxy = self._default_proxy
682 if proxy_auth is None:
683 proxy_auth = self._default_proxy_auth
685 if proxy is None:
686 proxy_headers = None
687 else:
688 proxy_headers = self._prepare_headers(proxy_headers)
689 try:
690 proxy = URL(proxy)
691 except ValueError as e:
692 raise InvalidURL(proxy) from e
694 if timeout is sentinel:
695 real_timeout: ClientTimeout = self._timeout
696 else:
697 if not isinstance(timeout, ClientTimeout):
698 real_timeout = ClientTimeout(total=timeout)
699 else:
700 real_timeout = timeout
701 # timeout is cumulative for all request operations
702 # (request, redirects, responses, data consuming)
703 tm = TimeoutHandle(
704 self._loop,
705 real_timeout.total,
706 ceil_threshold=real_timeout.ceil_threshold,
707 )
708 handle = tm.start()
710 if read_bufsize is None:
711 read_bufsize = self._read_bufsize
713 if auto_decompress is None:
714 auto_decompress = self._auto_decompress
716 if max_line_size is None:
717 max_line_size = self._max_line_size
719 if max_field_size is None:
720 max_field_size = self._max_field_size
722 if max_headers is None:
723 max_headers = self._max_headers
725 for trace_config in self._trace_configs:
726 trace = Trace(
727 self,
728 trace_config,
729 trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
730 )
731 await trace.send_request_start(
732 method, url.update_query(params), headers
733 )
734 traces.append(trace)
736 timer = tm.timer()
737 with timer:
738 # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests
739 retry_persistent_connection = (
740 self._retry_connection and method in IDEMPOTENT_METHODS
741 )
742 while True:
743 url, auth_from_url = strip_auth_from_url(url)
744 if not url.raw_host:
745 # NOTE: Bail early, otherwise, causes `InvalidURL` through
746 # NOTE: `self._request_class()` below.
747 err_exc_cls = (
748 InvalidUrlRedirectClientError
749 if redirects
750 else InvalidUrlClientError
751 )
752 raise err_exc_cls(url)
753 # If `auth` was passed for an already authenticated URL,
754 # disallow only if this is the initial URL; this is to avoid issues
755 # with sketchy redirects that are not the caller's responsibility
756 if not history and (auth and auth_from_url):
757 raise ValueError(
758 "Cannot combine AUTH argument with "
759 "credentials encoded in URL"
760 )
762 # Override the auth with the one from the URL only if we
763 # have no auth, or if we got an auth from a redirect URL
764 if auth is None or (history and auth_from_url is not None):
765 auth = auth_from_url
767 if (
768 auth is None
769 and self._default_auth
770 and (
771 not self._base_url or self._base_url_origin == url.origin()
772 )
773 ):
774 auth = self._default_auth
776 # Try netrc if auth is still None and trust_env is enabled.
777 if auth is None and self._trust_env and url.host is not None:
778 auth = await self._loop.run_in_executor(
779 None, self._get_netrc_auth, url.host
780 )
782 # It would be confusing if we support explicit
783 # Authorization header with auth argument
784 if (
785 headers is not None
786 and auth is not None
787 and hdrs.AUTHORIZATION in headers
788 ):
789 raise ValueError(
790 "Cannot combine AUTHORIZATION header "
791 "with AUTH argument or credentials "
792 "encoded in URL"
793 )
795 all_cookies = self._cookie_jar.filter_cookies(url)
797 if cookies is not None:
798 tmp_cookie_jar = _UnlimitedCookieJar(
799 unsafe=self._cookie_jar.unsafe,
800 quote_cookie=self._cookie_jar.quote_cookie,
801 treat_as_secure_origin=frozenset(
802 self._cookie_jar.treat_as_secure_origin
803 ),
804 )
805 tmp_cookie_jar.update_cookies(cookies)
806 req_cookies = tmp_cookie_jar.filter_cookies(url)
807 if req_cookies:
808 all_cookies.load(req_cookies)
810 proxy_: URL | None = None
811 if proxy is not None:
812 proxy_ = URL(proxy)
813 elif self._trust_env:
814 with suppress(LookupError):
815 proxy_, proxy_auth = await asyncio.to_thread(
816 get_env_proxy_for_url, url
817 )
819 req = self._request_class(
820 method,
821 url,
822 params=params,
823 headers=headers,
824 skip_auto_headers=skip_headers,
825 data=data,
826 cookies=all_cookies,
827 auth=auth,
828 version=version,
829 compress=compress,
830 chunked=chunked,
831 expect100=expect100,
832 loop=self._loop,
833 response_class=self._response_class,
834 proxy=proxy_,
835 proxy_auth=proxy_auth,
836 timer=timer,
837 session=self,
838 ssl=ssl if ssl is not None else True,
839 server_hostname=server_hostname,
840 proxy_headers=proxy_headers,
841 traces=traces,
842 trust_env=self.trust_env,
843 )
844 req._upload_tracker = upload_tracker
846 async def _connect_and_send_request(
847 req: ClientRequest,
848 ) -> ClientResponse:
849 # connection timeout
850 assert self._connector is not None
851 if (tracker := req._upload_tracker) is not None:
852 req._upload_gen = tracker._attempt_started()
853 try:
854 conn = await self._connector.connect(
855 req, traces=traces, timeout=real_timeout
856 )
857 except asyncio.TimeoutError as exc:
858 raise ConnectionTimeoutError(
859 f"Connection timeout to host {req.url}"
860 ) from exc
862 assert conn.protocol is not None
863 conn.protocol.set_response_params(
864 timer=timer,
865 skip_payload=req.method in EMPTY_BODY_METHODS,
866 read_until_eof=read_until_eof,
867 auto_decompress=auto_decompress,
868 read_timeout=real_timeout.sock_read,
869 read_bufsize=read_bufsize,
870 timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
871 max_line_size=max_line_size,
872 max_field_size=max_field_size,
873 max_headers=max_headers,
874 )
875 try:
876 resp = await req.send(conn)
877 try:
878 await resp.start(conn)
879 except BaseException:
880 resp.close()
881 raise
882 except BaseException:
883 conn.close()
884 raise
885 return resp
887 # Apply middleware (if any) - per-request middleware overrides session middleware
888 effective_middlewares = (
889 self._middlewares if middlewares is None else middlewares
890 )
892 if effective_middlewares:
893 handler = build_client_middlewares(
894 _connect_and_send_request, effective_middlewares
895 )
896 else:
897 handler = _connect_and_send_request
899 try:
900 resp = await handler(req)
901 # Client connector errors should not be retried
902 except (
903 ConnectionTimeoutError,
904 ClientConnectorError,
905 ClientConnectorCertificateError,
906 ClientConnectorSSLError,
907 ):
908 raise
909 except (ClientOSError, ServerDisconnectedError):
910 if not retry_persistent_connection:
911 raise
912 retry_persistent_connection = False
913 if data is not None:
914 # Rebuilding from `data` would resend only the unread
915 # remainder of a file object; reuse the payload, which
916 # rewinds itself once the cancelled writer has settled.
917 await req.close()
918 if req._body is not None and req._body.consumed:
919 raise
920 data = req._body
921 continue
922 except ClientError:
923 raise
924 except OSError as exc:
925 if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
926 raise
927 raise ClientOSError(*exc.args) from exc
929 # Update cookies from raw headers to preserve duplicates
930 if resp._raw_cookie_headers:
931 self._cookie_jar.update_cookies_from_headers(
932 resp._raw_cookie_headers, resp.url
933 )
935 # redirects
936 if resp.status in (301, 302, 303, 307, 308) and allow_redirects:
938 for trace in traces:
939 await trace.send_request_redirect(
940 method, url.update_query(params), headers, resp
941 )
943 redirects += 1
944 history.append(resp)
945 if max_redirects and redirects >= max_redirects:
946 if req._body is not None:
947 await req._body.close()
948 resp.close()
949 raise TooManyRedirects(
950 history[0].request_info, tuple(history)
951 )
953 # For 301 and 302, mimic IE, now changed in RFC
954 # https://github.com/kennethreitz/requests/pull/269
955 if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (
956 resp.status in (301, 302) and resp.method == hdrs.METH_POST
957 ):
958 method = hdrs.METH_GET
959 data = None
960 if headers.get(hdrs.CONTENT_LENGTH):
961 headers.pop(hdrs.CONTENT_LENGTH)
962 else:
963 # For 307/308, always preserve the request body
964 # For 301/302 with non-POST methods, preserve the request body
965 # https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1
966 # Use the existing payload to avoid recreating it from
967 # a potentially consumed file.
968 #
969 # If the payload is already consumed and cannot be replayed,
970 # fail fast instead of silently sending an empty body.
971 if req._body is not None and req._body.consumed:
972 resp.close()
973 raise ClientPayloadError(
974 "Cannot follow redirect with a consumed request "
975 "body. Use bytes, a seekable file-like object, "
976 "or set allow_redirects=False."
977 )
978 data = req._body
980 r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
981 hdrs.URI
982 )
983 if r_url is None:
984 # see github.com/aio-libs/aiohttp/issues/2022
985 break
986 else:
987 # reading from correct redirection
988 # response is forbidden
989 resp.release()
991 try:
992 parsed_redirect_url = URL(
993 r_url, encoded=not self._requote_redirect_url
994 )
995 except ValueError as e:
996 if req._body is not None:
997 await req._body.close()
998 resp.close()
999 raise InvalidUrlRedirectClientError(
1000 r_url,
1001 "Server attempted redirecting to a location that does not look like a URL",
1002 ) from e
1004 scheme = parsed_redirect_url.scheme
1005 if scheme not in HTTP_AND_EMPTY_SCHEMA_SET:
1006 if req._body is not None:
1007 await req._body.close()
1008 resp.close()
1009 raise NonHttpUrlRedirectClientError(r_url)
1010 elif not scheme or (
1011 scheme == url.scheme and _has_no_authority(r_url, scheme)
1012 ):
1013 # "http:/path" or "http:path" is a reference to
1014 # the current URL, as browsers resolve it.
1015 parsed_redirect_url = url.join(parsed_redirect_url)
1017 try:
1018 redirect_origin = parsed_redirect_url.origin()
1019 except ValueError as origin_val_err:
1020 if req._body is not None:
1021 await req._body.close()
1022 resp.close()
1023 raise InvalidUrlRedirectClientError(
1024 parsed_redirect_url,
1025 "Invalid redirect URL origin",
1026 ) from origin_val_err
1028 if url.origin() != redirect_origin:
1029 auth = None
1030 cookies = None
1031 headers.popall(hdrs.AUTHORIZATION, None)
1032 headers.popall(hdrs.COOKIE, None)
1033 headers.popall(hdrs.PROXY_AUTHORIZATION, None)
1035 url = parsed_redirect_url
1036 params = {}
1037 resp.release()
1038 continue
1040 break
1042 if req._body is not None:
1043 await req._body.close()
1044 # check response status
1045 if raise_for_status is None:
1046 raise_for_status = self._raise_for_status
1048 if raise_for_status is None:
1049 pass
1050 elif callable(raise_for_status):
1051 await raise_for_status(resp)
1052 elif raise_for_status:
1053 resp.raise_for_status()
1055 # register connection
1056 if handle is not None:
1057 if resp.connection is not None:
1058 resp.connection.add_callback(handle.cancel)
1059 else:
1060 handle.cancel()
1062 resp._history = tuple(history)
1064 for trace in traces:
1065 await trace.send_request_end(
1066 method, url.update_query(params), headers, resp
1067 )
1068 if upload_tracker is not None:
1069 upload_tracker._finalize()
1070 return resp
1072 except BaseException as e:
1073 # cleanup timer
1074 if tm is not None:
1075 tm.close()
1076 if handle:
1077 handle.cancel()
1079 if upload_tracker is not None:
1080 upload_tracker._finalize()
1082 if resp is not None:
1083 # A failure occurred after the response was received.
1084 resp.close()
1086 if req is not None and req._body is not None:
1087 await req._body.close()
1089 for trace in traces:
1090 await trace.send_request_exception(
1091 method, url.update_query(params), headers, e # type: ignore[arg-type]
1092 )
1093 raise
1095 if sys.version_info >= (3, 11) and TYPE_CHECKING:
1097 @overload
1098 def ws_connect(
1099 self,
1100 url: StrOrURL,
1101 *,
1102 decode_text: Literal[True] = ...,
1103 **kwargs: Unpack[_WSConnectOptions],
1104 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[Literal[True]]]": ...
1106 @overload
1107 def ws_connect(
1108 self,
1109 url: StrOrURL,
1110 *,
1111 decode_text: Literal[False],
1112 **kwargs: Unpack[_WSConnectOptions],
1113 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[Literal[False]]]": ...
1115 @overload
1116 def ws_connect(
1117 self,
1118 url: StrOrURL,
1119 *,
1120 decode_text: bool = ...,
1121 **kwargs: Unpack[_WSConnectOptions],
1122 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[bool]]": ...
1124 def ws_connect(
1125 self,
1126 url: StrOrURL,
1127 *,
1128 method: str = hdrs.METH_GET,
1129 protocols: Iterable[str] = (),
1130 timeout: ClientWSTimeout | _SENTINEL = sentinel,
1131 receive_timeout: float | None = None,
1132 autoclose: bool = True,
1133 autoping: bool = True,
1134 heartbeat: float | None = None,
1135 auth: BasicAuth | None = None,
1136 origin: str | None = None,
1137 params: Query = None,
1138 headers: LooseHeaders | None = None,
1139 proxy: StrOrURL | None = None,
1140 proxy_auth: BasicAuth | None = None,
1141 ssl: SSLContext | bool | Fingerprint = True,
1142 verify_ssl: bool | None = None,
1143 fingerprint: bytes | None = None,
1144 ssl_context: SSLContext | None = None,
1145 server_hostname: str | None = None,
1146 proxy_headers: LooseHeaders | None = None,
1147 compress: int = 0,
1148 max_msg_size: int = 4 * 1024 * 1024,
1149 decode_text: bool = True,
1150 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[bool]]":
1151 """Initiate websocket connection."""
1152 return _WSRequestContextManager(
1153 self._ws_connect(
1154 url,
1155 method=method,
1156 protocols=protocols,
1157 timeout=timeout,
1158 receive_timeout=receive_timeout,
1159 autoclose=autoclose,
1160 autoping=autoping,
1161 heartbeat=heartbeat,
1162 auth=auth,
1163 origin=origin,
1164 params=params,
1165 headers=headers,
1166 proxy=proxy,
1167 proxy_auth=proxy_auth,
1168 ssl=ssl,
1169 verify_ssl=verify_ssl,
1170 fingerprint=fingerprint,
1171 ssl_context=ssl_context,
1172 server_hostname=server_hostname,
1173 proxy_headers=proxy_headers,
1174 compress=compress,
1175 max_msg_size=max_msg_size,
1176 decode_text=decode_text,
1177 )
1178 )
1180 if sys.version_info >= (3, 11) and TYPE_CHECKING:
1182 @overload
1183 async def _ws_connect(
1184 self,
1185 url: StrOrURL,
1186 *,
1187 decode_text: Literal[True] = ...,
1188 **kwargs: Unpack[_WSConnectOptions],
1189 ) -> "ClientWebSocketResponse[Literal[True]]": ...
1191 @overload
1192 async def _ws_connect(
1193 self,
1194 url: StrOrURL,
1195 *,
1196 decode_text: Literal[False],
1197 **kwargs: Unpack[_WSConnectOptions],
1198 ) -> "ClientWebSocketResponse[Literal[False]]": ...
1200 @overload
1201 async def _ws_connect(
1202 self,
1203 url: StrOrURL,
1204 *,
1205 decode_text: bool = ...,
1206 **kwargs: Unpack[_WSConnectOptions],
1207 ) -> "ClientWebSocketResponse[bool]": ...
1209 async def _ws_connect(
1210 self,
1211 url: StrOrURL,
1212 *,
1213 method: str = hdrs.METH_GET,
1214 protocols: Iterable[str] = (),
1215 timeout: ClientWSTimeout | _SENTINEL = sentinel,
1216 receive_timeout: float | None = None,
1217 autoclose: bool = True,
1218 autoping: bool = True,
1219 heartbeat: float | None = None,
1220 auth: BasicAuth | None = None,
1221 origin: str | None = None,
1222 params: Query = None,
1223 headers: LooseHeaders | None = None,
1224 proxy: StrOrURL | None = None,
1225 proxy_auth: BasicAuth | None = None,
1226 ssl: SSLContext | bool | Fingerprint = True,
1227 verify_ssl: bool | None = None,
1228 fingerprint: bytes | None = None,
1229 ssl_context: SSLContext | None = None,
1230 server_hostname: str | None = None,
1231 proxy_headers: LooseHeaders | None = None,
1232 compress: int = 0,
1233 max_msg_size: int = 4 * 1024 * 1024,
1234 decode_text: bool = True,
1235 ) -> "ClientWebSocketResponse[bool]":
1236 if auth is not None:
1237 warnings.warn(
1238 "The 'auth' parameter is deprecated and will be removed in v4;"
1239 " pass headers={'Authorization': "
1240 "aiohttp.encode_basic_auth(login, password)} instead",
1241 DeprecationWarning,
1242 stacklevel=3,
1243 )
1244 if proxy_auth is not None:
1245 warnings.warn(
1246 "The 'proxy_auth' parameter is deprecated and will be removed in v4;"
1247 " pass proxy_headers={'Proxy-Authorization': "
1248 "aiohttp.encode_basic_auth(login, password)} instead",
1249 DeprecationWarning,
1250 stacklevel=3,
1251 )
1252 if timeout is not sentinel:
1253 if isinstance(timeout, ClientWSTimeout):
1254 ws_timeout = timeout
1255 else:
1256 warnings.warn(
1257 "parameter 'timeout' of type 'float' "
1258 "is deprecated, please use "
1259 "'timeout=ClientWSTimeout(ws_close=...)'",
1260 DeprecationWarning,
1261 stacklevel=2,
1262 )
1263 ws_timeout = ClientWSTimeout(ws_close=timeout)
1264 else:
1265 ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT
1266 if receive_timeout is not None:
1267 warnings.warn(
1268 "float parameter 'receive_timeout' "
1269 "is deprecated, please use parameter "
1270 "'timeout=ClientWSTimeout(ws_receive=...)'",
1271 DeprecationWarning,
1272 stacklevel=2,
1273 )
1274 ws_timeout = attr.evolve(ws_timeout, ws_receive=receive_timeout)
1276 if headers is None:
1277 real_headers: CIMultiDict[str] = CIMultiDict()
1278 else:
1279 real_headers = CIMultiDict(headers)
1281 default_headers = {
1282 hdrs.UPGRADE: "websocket",
1283 hdrs.CONNECTION: "Upgrade",
1284 hdrs.SEC_WEBSOCKET_VERSION: "13",
1285 }
1287 for key, value in default_headers.items():
1288 real_headers.setdefault(key, value)
1290 sec_key = base64.b64encode(os.urandom(16))
1291 real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()
1293 if protocols:
1294 real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols)
1295 if origin is not None:
1296 real_headers[hdrs.ORIGIN] = origin
1297 if compress:
1298 extstr = ws_ext_gen(compress=compress)
1299 real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr
1301 # For the sake of backward compatibility, if user passes in None, convert it to True
1302 if ssl is None:
1303 warnings.warn(
1304 "ssl=None is deprecated, please use ssl=True",
1305 DeprecationWarning,
1306 stacklevel=2,
1307 )
1308 ssl = True
1309 ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
1311 # send request
1312 resp = await self.request(
1313 method,
1314 url,
1315 params=params,
1316 headers=real_headers,
1317 read_until_eof=False,
1318 auth=auth,
1319 proxy=proxy,
1320 proxy_auth=proxy_auth,
1321 ssl=ssl,
1322 server_hostname=server_hostname,
1323 proxy_headers=proxy_headers,
1324 )
1326 try:
1327 # check handshake
1328 if resp.status != 101:
1329 raise WSServerHandshakeError(
1330 resp.request_info,
1331 resp.history,
1332 message="Invalid response status",
1333 status=resp.status,
1334 headers=resp.headers,
1335 )
1337 if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket":
1338 raise WSServerHandshakeError(
1339 resp.request_info,
1340 resp.history,
1341 message="Invalid upgrade header",
1342 status=resp.status,
1343 headers=resp.headers,
1344 )
1346 if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade":
1347 raise WSServerHandshakeError(
1348 resp.request_info,
1349 resp.history,
1350 message="Invalid connection header",
1351 status=resp.status,
1352 headers=resp.headers,
1353 )
1355 # key calculation
1356 r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")
1357 match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
1358 if r_key != match:
1359 raise WSServerHandshakeError(
1360 resp.request_info,
1361 resp.history,
1362 message="Invalid challenge response",
1363 status=resp.status,
1364 headers=resp.headers,
1365 )
1367 # websocket protocol
1368 protocol = None
1369 if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:
1370 resp_protocols = [
1371 proto.strip()
1372 for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
1373 ]
1375 for proto in resp_protocols:
1376 if proto in protocols:
1377 protocol = proto
1378 break
1380 # websocket compress
1381 notakeover = False
1382 if compress:
1383 compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)
1384 if compress_hdrs:
1385 try:
1386 compress, notakeover = ws_ext_parse(compress_hdrs)
1387 except WSHandshakeError as exc:
1388 raise WSServerHandshakeError(
1389 resp.request_info,
1390 resp.history,
1391 message=exc.args[0],
1392 status=resp.status,
1393 headers=resp.headers,
1394 ) from exc
1395 else:
1396 compress = 0
1397 notakeover = False
1399 conn = resp.connection
1400 assert conn is not None
1401 conn_proto = conn.protocol
1402 assert conn_proto is not None
1404 # For WS connection the read_timeout must be either receive_timeout or greater
1405 # None == no timeout, i.e. infinite timeout, so None is the max timeout possible
1406 if ws_timeout.ws_receive is None:
1407 # Reset regardless
1408 conn_proto.read_timeout = None
1409 elif conn_proto.read_timeout is not None:
1410 conn_proto.read_timeout = max(
1411 ws_timeout.ws_receive, conn_proto.read_timeout
1412 )
1414 transport = conn.transport
1415 assert transport is not None
1416 reader = WebSocketDataQueue(conn_proto, DEFAULT_CHUNK_SIZE, loop=self._loop)
1417 writer = WebSocketWriter(
1418 conn_proto,
1419 transport,
1420 use_mask=True,
1421 compress=compress,
1422 notakeover=notakeover,
1423 )
1424 except BaseException:
1425 resp.close()
1426 raise
1427 else:
1428 ws_resp = self._ws_response_class(
1429 reader,
1430 writer,
1431 protocol,
1432 resp,
1433 ws_timeout,
1434 autoclose,
1435 autoping,
1436 self._loop,
1437 heartbeat=heartbeat,
1438 compress=compress,
1439 client_notakeover=notakeover,
1440 )
1441 ws_resp._parser = WebSocketReader(
1442 reader,
1443 max_msg_size,
1444 compress=bool(compress),
1445 decode_text=decode_text,
1446 )
1447 cb = None if heartbeat is None else ws_resp._on_data_received
1448 conn_proto.set_parser(ws_resp._parser, reader, data_received_cb=cb)
1449 return ws_resp
1451 def _prepare_headers(self, headers: LooseHeaders | None) -> "CIMultiDict[str]":
1452 """Add default headers and transform it to CIMultiDict"""
1453 # Convert headers to MultiDict
1454 result = CIMultiDict(self._default_headers)
1455 if headers:
1456 if not isinstance(headers, (MultiDictProxy, MultiDict)):
1457 headers = CIMultiDict(headers)
1458 added_names: set[str] = set()
1459 for key, value in headers.items():
1460 if key in added_names:
1461 result.add(key, value)
1462 else:
1463 result[key] = value
1464 added_names.add(key)
1465 return result
1467 def _get_netrc_auth(self, host: str) -> BasicAuth | None:
1468 """
1469 Get auth from netrc for the given host.
1471 This method is designed to be called in an executor to avoid
1472 blocking I/O in the event loop.
1473 """
1474 netrc_obj = netrc_from_env()
1475 try:
1476 return basicauth_from_netrc(netrc_obj, host)
1477 except LookupError:
1478 return None
1480 if sys.version_info >= (3, 11) and TYPE_CHECKING:
1482 def get(
1483 self,
1484 url: StrOrURL,
1485 **kwargs: Unpack[_RequestOptions],
1486 ) -> "_RequestContextManager": ...
1488 def options(
1489 self,
1490 url: StrOrURL,
1491 **kwargs: Unpack[_RequestOptions],
1492 ) -> "_RequestContextManager": ...
1494 def head(
1495 self,
1496 url: StrOrURL,
1497 **kwargs: Unpack[_RequestOptions],
1498 ) -> "_RequestContextManager": ...
1500 def post(
1501 self,
1502 url: StrOrURL,
1503 **kwargs: Unpack[_RequestOptions],
1504 ) -> "_RequestContextManager": ...
1506 def put(
1507 self,
1508 url: StrOrURL,
1509 **kwargs: Unpack[_RequestOptions],
1510 ) -> "_RequestContextManager": ...
1512 def patch(
1513 self,
1514 url: StrOrURL,
1515 **kwargs: Unpack[_RequestOptions],
1516 ) -> "_RequestContextManager": ...
1518 def delete(
1519 self,
1520 url: StrOrURL,
1521 **kwargs: Unpack[_RequestOptions],
1522 ) -> "_RequestContextManager": ...
1524 else:
1526 def get(
1527 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
1528 ) -> "_RequestContextManager":
1529 """Perform HTTP GET request."""
1530 return _RequestContextManager(
1531 self._request(
1532 hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs
1533 )
1534 )
1536 def options(
1537 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
1538 ) -> "_RequestContextManager":
1539 """Perform HTTP OPTIONS request."""
1540 return _RequestContextManager(
1541 self._request(
1542 hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs
1543 )
1544 )
1546 def head(
1547 self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any
1548 ) -> "_RequestContextManager":
1549 """Perform HTTP HEAD request."""
1550 return _RequestContextManager(
1551 self._request(
1552 hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs
1553 )
1554 )
1556 def post(
1557 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1558 ) -> "_RequestContextManager":
1559 """Perform HTTP POST request."""
1560 return _RequestContextManager(
1561 self._request(hdrs.METH_POST, url, data=data, **kwargs)
1562 )
1564 def put(
1565 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1566 ) -> "_RequestContextManager":
1567 """Perform HTTP PUT request."""
1568 return _RequestContextManager(
1569 self._request(hdrs.METH_PUT, url, data=data, **kwargs)
1570 )
1572 def patch(
1573 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1574 ) -> "_RequestContextManager":
1575 """Perform HTTP PATCH request."""
1576 return _RequestContextManager(
1577 self._request(hdrs.METH_PATCH, url, data=data, **kwargs)
1578 )
1580 def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager":
1581 """Perform HTTP DELETE request."""
1582 return _RequestContextManager(
1583 self._request(hdrs.METH_DELETE, url, **kwargs)
1584 )
1586 async def close(self) -> None:
1587 """Close underlying connector.
1589 Release all acquired resources.
1590 """
1591 if not self.closed:
1592 if self._connector is not None and self._connector_owner:
1593 await self._connector.close()
1594 self._connector = None
1596 @property
1597 def closed(self) -> bool:
1598 """Is client session closed.
1600 A readonly property.
1601 """
1602 return self._connector is None or self._connector.closed
1604 @property
1605 def connector(self) -> BaseConnector | None:
1606 """Connector instance used for the session."""
1607 return self._connector
1609 @property
1610 def cookie_jar(self) -> AbstractCookieJar:
1611 """The session cookies."""
1612 return self._cookie_jar
1614 @property
1615 def version(self) -> tuple[int, int]:
1616 """The session HTTP protocol version."""
1617 return self._version
1619 @property
1620 def requote_redirect_url(self) -> bool:
1621 """Do URL requoting on redirection handling."""
1622 return self._requote_redirect_url
1624 @requote_redirect_url.setter
1625 def requote_redirect_url(self, val: bool) -> None:
1626 """Do URL requoting on redirection handling."""
1627 warnings.warn(
1628 "session.requote_redirect_url modification is deprecated #2778",
1629 DeprecationWarning,
1630 stacklevel=2,
1631 )
1632 self._requote_redirect_url = val
1634 @property
1635 def loop(self) -> asyncio.AbstractEventLoop:
1636 """Session's loop."""
1637 warnings.warn(
1638 "client.loop property is deprecated", DeprecationWarning, stacklevel=2
1639 )
1640 return self._loop
1642 @property
1643 def timeout(self) -> ClientTimeout:
1644 """Timeout for the session."""
1645 return self._timeout
1647 @property
1648 def headers(self) -> "CIMultiDict[str]":
1649 """The default headers of the client session."""
1650 return self._default_headers
1652 @property
1653 def skip_auto_headers(self) -> frozenset[istr]:
1654 """Headers for which autogeneration should be skipped"""
1655 return self._skip_auto_headers
1657 @property
1658 def auth(self) -> BasicAuth | None:
1659 """An object that represents HTTP Basic Authorization"""
1660 return self._default_auth
1662 @property
1663 def json_serialize(self) -> JSONEncoder:
1664 """Json serializer callable"""
1665 return self._json_serialize
1667 @property
1668 def connector_owner(self) -> bool:
1669 """Should connector be closed on session closing"""
1670 return self._connector_owner
1672 @property
1673 def raise_for_status(
1674 self,
1675 ) -> bool | Callable[[ClientResponse], Awaitable[None]]:
1676 """Should `ClientResponse.raise_for_status()` be called for each response."""
1677 return self._raise_for_status
1679 @property
1680 def auto_decompress(self) -> bool:
1681 """Should the body response be automatically decompressed."""
1682 return self._auto_decompress
1684 @property
1685 def trust_env(self) -> bool:
1686 """
1687 Should proxies information from environment or netrc be trusted.
1689 Information is from HTTP_PROXY / HTTPS_PROXY environment variables
1690 or ~/.netrc file if present.
1691 """
1692 return self._trust_env
1694 @property
1695 def trace_configs(self) -> list[TraceConfig]:
1696 """A list of TraceConfig instances used for client tracing"""
1697 return self._trace_configs
1699 def detach(self) -> None:
1700 """Detach connector from session without closing the former.
1702 Session is switched to closed state anyway.
1703 """
1704 self._connector = None
1706 def __enter__(self) -> None:
1707 raise TypeError("Use async with instead")
1709 def __exit__(
1710 self,
1711 exc_type: type[BaseException] | None,
1712 exc_val: BaseException | None,
1713 exc_tb: TracebackType | None,
1714 ) -> None:
1715 # __exit__ should exist in pair with __enter__ but never executed
1716 pass # pragma: no cover
1718 async def __aenter__(self) -> "ClientSession":
1719 return self
1721 async def __aexit__(
1722 self,
1723 exc_type: type[BaseException] | None,
1724 exc_val: BaseException | None,
1725 exc_tb: TracebackType | None,
1726 ) -> None:
1727 await self.close()
1730class _BaseRequestContextManager(
1731 Coroutine[Any, Any, _RetType_co], Generic[_RetType_co]
1732):
1734 __slots__ = ("_coro", "_resp")
1736 def __init__(self, coro: Coroutine[asyncio.Future[Any], None, _RetType_co]) -> None:
1737 self._coro: Coroutine[asyncio.Future[Any], None, _RetType_co] = coro
1739 def send(self, arg: None) -> asyncio.Future[Any]:
1740 return self._coro.send(arg)
1742 def throw(self, *args: Any, **kwargs: Any) -> asyncio.Future[Any]:
1743 return self._coro.throw(*args, **kwargs)
1745 def close(self) -> None:
1746 return self._coro.close()
1748 def __await__(self) -> Generator[Any, None, _RetType_co]:
1749 ret = self._coro.__await__()
1750 return ret
1752 def __iter__(self) -> Generator[Any, None, _RetType_co]:
1753 return self.__await__()
1755 async def __aenter__(self) -> _RetType_co:
1756 self._resp: _RetType_co = await self._coro
1757 return await self._resp.__aenter__() # type: ignore[return-value]
1759 async def __aexit__(
1760 self,
1761 exc_type: type[BaseException] | None,
1762 exc: BaseException | None,
1763 tb: TracebackType | None,
1764 ) -> None:
1765 await self._resp.__aexit__(exc_type, exc, tb)
1768_RequestContextManager = _BaseRequestContextManager[ClientResponse]
1769_WSRequestContextManager = _BaseRequestContextManager[ClientWebSocketResponse[bool]]
1772class _SessionRequestContextManager:
1774 __slots__ = ("_coro", "_resp", "_session")
1776 def __init__(
1777 self,
1778 coro: Coroutine[asyncio.Future[Any], None, ClientResponse],
1779 session: ClientSession,
1780 ) -> None:
1781 self._coro = coro
1782 self._resp: ClientResponse | None = None
1783 self._session = session
1785 async def __aenter__(self) -> ClientResponse:
1786 try:
1787 self._resp = await self._coro
1788 except BaseException:
1789 await self._session.close()
1790 raise
1791 else:
1792 return self._resp
1794 async def __aexit__(
1795 self,
1796 exc_type: type[BaseException] | None,
1797 exc: BaseException | None,
1798 tb: TracebackType | None,
1799 ) -> None:
1800 assert self._resp is not None
1801 self._resp.close()
1802 await self._session.close()
1805if sys.version_info >= (3, 11) and TYPE_CHECKING:
1807 def request(
1808 method: str,
1809 url: StrOrURL,
1810 *,
1811 version: HttpVersion = http.HttpVersion11,
1812 connector: BaseConnector | None = None,
1813 loop: asyncio.AbstractEventLoop | None = None,
1814 **kwargs: Unpack[_RequestOptions],
1815 ) -> _SessionRequestContextManager: ...
1817else:
1819 def request(
1820 method: str,
1821 url: StrOrURL,
1822 *,
1823 version: HttpVersion = http.HttpVersion11,
1824 connector: BaseConnector | None = None,
1825 loop: asyncio.AbstractEventLoop | None = None,
1826 **kwargs: Any,
1827 ) -> _SessionRequestContextManager:
1828 """Constructs and sends a request.
1830 Returns response object.
1831 method - HTTP method
1832 url - request url
1833 params - (optional) Dictionary or bytes to be sent in the query
1834 string of the new request
1835 data - (optional) Dictionary, bytes, or file-like object to
1836 send in the body of the request
1837 json - (optional) Any json compatible python object
1838 headers - (optional) Dictionary of HTTP Headers to send with
1839 the request
1840 cookies - (optional) Dict object to send with the request
1841 auth - (optional) BasicAuth named tuple represent HTTP Basic Auth
1842 auth - aiohttp.helpers.BasicAuth
1843 allow_redirects - (optional) If set to False, do not follow
1844 redirects
1845 version - Request HTTP version.
1846 compress - Set to True if request has to be compressed
1847 with deflate encoding.
1848 chunked - Set to chunk size for chunked transfer encoding.
1849 expect100 - Expect 100-continue response from server.
1850 connector - BaseConnector sub-class instance to support
1851 connection pooling.
1852 read_until_eof - Read response until eof if response
1853 does not have Content-Length header.
1854 loop - Optional event loop.
1855 timeout - Optional ClientTimeout settings structure, 5min
1856 total timeout by default.
1857 Usage::
1858 >>> import aiohttp
1859 >>> async with aiohttp.request('GET', 'http://python.org/') as resp:
1860 ... print(resp)
1861 ... data = await resp.read()
1862 <ClientResponse(https://www.python.org/) [200 OK]>
1863 """
1864 connector_owner = False
1865 if connector is None:
1866 connector_owner = True
1867 connector = TCPConnector(loop=loop, force_close=True)
1869 session = ClientSession(
1870 loop=loop,
1871 cookies=kwargs.pop("cookies", None),
1872 version=version,
1873 timeout=kwargs.pop("timeout", sentinel),
1874 connector=connector,
1875 connector_owner=connector_owner,
1876 )
1878 return _SessionRequestContextManager(
1879 session._request(method, url, **kwargs),
1880 session,
1881 )