Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/aiohttp/client.py: 55%
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 method = method.upper()
584 ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
586 if auth is not None:
587 warnings.warn(
588 "The 'auth' parameter is deprecated and will be removed in v4;"
589 " pass headers={'Authorization': "
590 "aiohttp.encode_basic_auth(login, password)} instead",
591 DeprecationWarning,
592 stacklevel=3,
593 )
594 if proxy_auth is not None:
595 warnings.warn(
596 "The 'proxy_auth' parameter is deprecated and will be removed in v4;"
597 " pass proxy_headers={'Proxy-Authorization': "
598 "aiohttp.encode_basic_auth(login, password)} instead",
599 DeprecationWarning,
600 stacklevel=3,
601 )
603 if data is not None and json is not None:
604 raise ValueError(
605 "data and json parameters can not be used at the same time"
606 )
607 elif json is not None:
608 if self._json_serialize_bytes is not None:
609 data = payload.JsonBytesPayload(json, dumps=self._json_serialize_bytes)
610 else:
611 data = payload.JsonPayload(json, dumps=self._json_serialize)
613 if not isinstance(chunked, bool) and chunked is not None:
614 warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
616 redirects = 0
617 history: list[ClientResponse] = []
618 version = self._version
619 params = params or {}
621 # Merge with default headers and transform to CIMultiDict
622 headers = self._prepare_headers(headers)
624 try:
625 url = self._build_url(str_or_url)
626 except ValueError as e:
627 raise InvalidUrlClientError(str_or_url) from e
629 assert self._connector is not None
630 if url.scheme not in self._connector.allowed_protocol_schema_set:
631 raise NonHttpUrlClientError(url)
633 skip_headers: Iterable[istr] | None
634 if skip_auto_headers is not None:
635 skip_headers = {
636 istr(i) for i in skip_auto_headers
637 } | self._skip_auto_headers
638 elif self._skip_auto_headers:
639 skip_headers = self._skip_auto_headers
640 else:
641 skip_headers = None
643 if proxy is None:
644 proxy = self._default_proxy
645 if proxy_auth is None:
646 proxy_auth = self._default_proxy_auth
648 if proxy is None:
649 proxy_headers = None
650 else:
651 proxy_headers = self._prepare_headers(proxy_headers)
652 try:
653 proxy = URL(proxy)
654 except ValueError as e:
655 raise InvalidURL(proxy) from e
657 if timeout is sentinel:
658 real_timeout: ClientTimeout = self._timeout
659 else:
660 if not isinstance(timeout, ClientTimeout):
661 real_timeout = ClientTimeout(total=timeout)
662 else:
663 real_timeout = timeout
664 # timeout is cumulative for all request operations
665 # (request, redirects, responses, data consuming)
666 tm = TimeoutHandle(
667 self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold
668 )
669 handle = tm.start()
671 if read_bufsize is None:
672 read_bufsize = self._read_bufsize
674 if auto_decompress is None:
675 auto_decompress = self._auto_decompress
677 if max_line_size is None:
678 max_line_size = self._max_line_size
680 if max_field_size is None:
681 max_field_size = self._max_field_size
683 if max_headers is None:
684 max_headers = self._max_headers
686 traces = [
687 Trace(
688 self,
689 trace_config,
690 trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
691 )
692 for trace_config in self._trace_configs
693 ]
695 for trace in traces:
696 await trace.send_request_start(method, url.update_query(params), headers)
698 timer = tm.timer()
699 req: ClientRequest | None = None
700 try:
701 with timer:
702 # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests
703 retry_persistent_connection = (
704 self._retry_connection and method in IDEMPOTENT_METHODS
705 )
706 while True:
707 url, auth_from_url = strip_auth_from_url(url)
708 if not url.raw_host:
709 # NOTE: Bail early, otherwise, causes `InvalidURL` through
710 # NOTE: `self._request_class()` below.
711 err_exc_cls = (
712 InvalidUrlRedirectClientError
713 if redirects
714 else InvalidUrlClientError
715 )
716 raise err_exc_cls(url)
717 # If `auth` was passed for an already authenticated URL,
718 # disallow only if this is the initial URL; this is to avoid issues
719 # with sketchy redirects that are not the caller's responsibility
720 if not history and (auth and auth_from_url):
721 raise ValueError(
722 "Cannot combine AUTH argument with "
723 "credentials encoded in URL"
724 )
726 # Override the auth with the one from the URL only if we
727 # have no auth, or if we got an auth from a redirect URL
728 if auth is None or (history and auth_from_url is not None):
729 auth = auth_from_url
731 if (
732 auth is None
733 and self._default_auth
734 and (
735 not self._base_url or self._base_url_origin == url.origin()
736 )
737 ):
738 auth = self._default_auth
740 # Try netrc if auth is still None and trust_env is enabled.
741 if auth is None and self._trust_env and url.host is not None:
742 auth = await self._loop.run_in_executor(
743 None, self._get_netrc_auth, url.host
744 )
746 # It would be confusing if we support explicit
747 # Authorization header with auth argument
748 if (
749 headers is not None
750 and auth is not None
751 and hdrs.AUTHORIZATION in headers
752 ):
753 raise ValueError(
754 "Cannot combine AUTHORIZATION header "
755 "with AUTH argument or credentials "
756 "encoded in URL"
757 )
759 all_cookies = self._cookie_jar.filter_cookies(url)
761 if cookies is not None:
762 tmp_cookie_jar = CookieJar(
763 unsafe=self._cookie_jar.unsafe,
764 quote_cookie=self._cookie_jar.quote_cookie,
765 )
766 tmp_cookie_jar.update_cookies(cookies)
767 req_cookies = tmp_cookie_jar.filter_cookies(url)
768 if req_cookies:
769 all_cookies.load(req_cookies)
771 proxy_: URL | None = None
772 if proxy is not None:
773 proxy_ = URL(proxy)
774 elif self._trust_env:
775 with suppress(LookupError):
776 proxy_, proxy_auth = await asyncio.to_thread(
777 get_env_proxy_for_url, url
778 )
780 req = self._request_class(
781 method,
782 url,
783 params=params,
784 headers=headers,
785 skip_auto_headers=skip_headers,
786 data=data,
787 cookies=all_cookies,
788 auth=auth,
789 version=version,
790 compress=compress,
791 chunked=chunked,
792 expect100=expect100,
793 loop=self._loop,
794 response_class=self._response_class,
795 proxy=proxy_,
796 proxy_auth=proxy_auth,
797 timer=timer,
798 session=self,
799 ssl=ssl if ssl is not None else True,
800 server_hostname=server_hostname,
801 proxy_headers=proxy_headers,
802 traces=traces,
803 trust_env=self.trust_env,
804 )
806 async def _connect_and_send_request(
807 req: ClientRequest,
808 ) -> ClientResponse:
809 # connection timeout
810 assert self._connector is not None
811 try:
812 conn = await self._connector.connect(
813 req, traces=traces, timeout=real_timeout
814 )
815 except asyncio.TimeoutError as exc:
816 raise ConnectionTimeoutError(
817 f"Connection timeout to host {req.url}"
818 ) from exc
820 assert conn.protocol is not None
821 conn.protocol.set_response_params(
822 timer=timer,
823 skip_payload=req.method in EMPTY_BODY_METHODS,
824 read_until_eof=read_until_eof,
825 auto_decompress=auto_decompress,
826 read_timeout=real_timeout.sock_read,
827 read_bufsize=read_bufsize,
828 timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
829 max_line_size=max_line_size,
830 max_field_size=max_field_size,
831 max_headers=max_headers,
832 )
833 try:
834 resp = await req.send(conn)
835 try:
836 await resp.start(conn)
837 except BaseException:
838 resp.close()
839 raise
840 except BaseException:
841 conn.close()
842 raise
843 return resp
845 # Apply middleware (if any) - per-request middleware overrides session middleware
846 effective_middlewares = (
847 self._middlewares if middlewares is None else middlewares
848 )
850 if effective_middlewares:
851 handler = build_client_middlewares(
852 _connect_and_send_request, effective_middlewares
853 )
854 else:
855 handler = _connect_and_send_request
857 try:
858 resp = await handler(req)
859 # Client connector errors should not be retried
860 except (
861 ConnectionTimeoutError,
862 ClientConnectorError,
863 ClientConnectorCertificateError,
864 ClientConnectorSSLError,
865 ):
866 raise
867 except (ClientOSError, ServerDisconnectedError):
868 if retry_persistent_connection:
869 retry_persistent_connection = False
870 continue
871 raise
872 except ClientError:
873 raise
874 except OSError as exc:
875 if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
876 raise
877 raise ClientOSError(*exc.args) from exc
879 # Update cookies from raw headers to preserve duplicates
880 if resp._raw_cookie_headers:
881 self._cookie_jar.update_cookies_from_headers(
882 resp._raw_cookie_headers, resp.url
883 )
885 # redirects
886 if resp.status in (301, 302, 303, 307, 308) and allow_redirects:
888 for trace in traces:
889 await trace.send_request_redirect(
890 method, url.update_query(params), headers, resp
891 )
893 redirects += 1
894 history.append(resp)
895 if max_redirects and redirects >= max_redirects:
896 if req._body is not None:
897 await req._body.close()
898 resp.close()
899 raise TooManyRedirects(
900 history[0].request_info, tuple(history)
901 )
903 # For 301 and 302, mimic IE, now changed in RFC
904 # https://github.com/kennethreitz/requests/pull/269
905 if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (
906 resp.status in (301, 302) and resp.method == hdrs.METH_POST
907 ):
908 method = hdrs.METH_GET
909 data = None
910 if headers.get(hdrs.CONTENT_LENGTH):
911 headers.pop(hdrs.CONTENT_LENGTH)
912 else:
913 # For 307/308, always preserve the request body
914 # For 301/302 with non-POST methods, preserve the request body
915 # https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1
916 # Use the existing payload to avoid recreating it from
917 # a potentially consumed file.
918 #
919 # If the payload is already consumed and cannot be replayed,
920 # fail fast instead of silently sending an empty body.
921 if req._body is not None and req._body.consumed:
922 resp.close()
923 raise ClientPayloadError(
924 "Cannot follow redirect with a consumed request "
925 "body. Use bytes, a seekable file-like object, "
926 "or set allow_redirects=False."
927 )
928 data = req._body
930 r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
931 hdrs.URI
932 )
933 if r_url is None:
934 # see github.com/aio-libs/aiohttp/issues/2022
935 break
936 else:
937 # reading from correct redirection
938 # response is forbidden
939 resp.release()
941 try:
942 parsed_redirect_url = URL(
943 r_url, encoded=not self._requote_redirect_url
944 )
945 except ValueError as e:
946 if req._body is not None:
947 await req._body.close()
948 resp.close()
949 raise InvalidUrlRedirectClientError(
950 r_url,
951 "Server attempted redirecting to a location that does not look like a URL",
952 ) from e
954 scheme = parsed_redirect_url.scheme
955 if scheme not in HTTP_AND_EMPTY_SCHEMA_SET:
956 if req._body is not None:
957 await req._body.close()
958 resp.close()
959 raise NonHttpUrlRedirectClientError(r_url)
960 elif not scheme:
961 parsed_redirect_url = url.join(parsed_redirect_url)
963 try:
964 redirect_origin = parsed_redirect_url.origin()
965 except ValueError as origin_val_err:
966 if req._body is not None:
967 await req._body.close()
968 resp.close()
969 raise InvalidUrlRedirectClientError(
970 parsed_redirect_url,
971 "Invalid redirect URL origin",
972 ) from origin_val_err
974 if url.origin() != redirect_origin:
975 auth = None
976 cookies = None
977 headers.popall(hdrs.AUTHORIZATION, None)
978 headers.popall(hdrs.COOKIE, None)
979 headers.popall(hdrs.PROXY_AUTHORIZATION, None)
981 url = parsed_redirect_url
982 params = {}
983 resp.release()
984 continue
986 break
988 if req._body is not None:
989 await req._body.close()
990 # check response status
991 if raise_for_status is None:
992 raise_for_status = self._raise_for_status
994 if raise_for_status is None:
995 pass
996 elif callable(raise_for_status):
997 await raise_for_status(resp)
998 elif raise_for_status:
999 resp.raise_for_status()
1001 # register connection
1002 if handle is not None:
1003 if resp.connection is not None:
1004 resp.connection.add_callback(handle.cancel)
1005 else:
1006 handle.cancel()
1008 resp._history = tuple(history)
1010 for trace in traces:
1011 await trace.send_request_end(
1012 method, url.update_query(params), headers, resp
1013 )
1014 return resp
1016 except BaseException as e:
1017 # cleanup timer
1018 tm.close()
1019 if handle:
1020 handle.cancel()
1021 handle = None
1023 if req is not None and req._body is not None:
1024 await req._body.close()
1026 for trace in traces:
1027 await trace.send_request_exception(
1028 method, url.update_query(params), headers, e
1029 )
1030 raise
1032 if sys.version_info >= (3, 11) and TYPE_CHECKING:
1034 @overload
1035 def ws_connect(
1036 self,
1037 url: StrOrURL,
1038 *,
1039 decode_text: Literal[True] = ...,
1040 **kwargs: Unpack[_WSConnectOptions],
1041 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[Literal[True]]]": ...
1043 @overload
1044 def ws_connect(
1045 self,
1046 url: StrOrURL,
1047 *,
1048 decode_text: Literal[False],
1049 **kwargs: Unpack[_WSConnectOptions],
1050 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[Literal[False]]]": ...
1052 @overload
1053 def ws_connect(
1054 self,
1055 url: StrOrURL,
1056 *,
1057 decode_text: bool = ...,
1058 **kwargs: Unpack[_WSConnectOptions],
1059 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[bool]]": ...
1061 def ws_connect(
1062 self,
1063 url: StrOrURL,
1064 *,
1065 method: str = hdrs.METH_GET,
1066 protocols: Iterable[str] = (),
1067 timeout: ClientWSTimeout | _SENTINEL = sentinel,
1068 receive_timeout: float | None = None,
1069 autoclose: bool = True,
1070 autoping: bool = True,
1071 heartbeat: float | None = None,
1072 auth: BasicAuth | None = None,
1073 origin: str | None = None,
1074 params: Query = None,
1075 headers: LooseHeaders | None = None,
1076 proxy: StrOrURL | None = None,
1077 proxy_auth: BasicAuth | None = None,
1078 ssl: SSLContext | bool | Fingerprint = True,
1079 verify_ssl: bool | None = None,
1080 fingerprint: bytes | None = None,
1081 ssl_context: SSLContext | None = None,
1082 server_hostname: str | None = None,
1083 proxy_headers: LooseHeaders | None = None,
1084 compress: int = 0,
1085 max_msg_size: int = 4 * 1024 * 1024,
1086 decode_text: bool = True,
1087 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[bool]]":
1088 """Initiate websocket connection."""
1089 return _WSRequestContextManager(
1090 self._ws_connect(
1091 url,
1092 method=method,
1093 protocols=protocols,
1094 timeout=timeout,
1095 receive_timeout=receive_timeout,
1096 autoclose=autoclose,
1097 autoping=autoping,
1098 heartbeat=heartbeat,
1099 auth=auth,
1100 origin=origin,
1101 params=params,
1102 headers=headers,
1103 proxy=proxy,
1104 proxy_auth=proxy_auth,
1105 ssl=ssl,
1106 verify_ssl=verify_ssl,
1107 fingerprint=fingerprint,
1108 ssl_context=ssl_context,
1109 server_hostname=server_hostname,
1110 proxy_headers=proxy_headers,
1111 compress=compress,
1112 max_msg_size=max_msg_size,
1113 decode_text=decode_text,
1114 )
1115 )
1117 if sys.version_info >= (3, 11) and TYPE_CHECKING:
1119 @overload
1120 async def _ws_connect(
1121 self,
1122 url: StrOrURL,
1123 *,
1124 decode_text: Literal[True] = ...,
1125 **kwargs: Unpack[_WSConnectOptions],
1126 ) -> "ClientWebSocketResponse[Literal[True]]": ...
1128 @overload
1129 async def _ws_connect(
1130 self,
1131 url: StrOrURL,
1132 *,
1133 decode_text: Literal[False],
1134 **kwargs: Unpack[_WSConnectOptions],
1135 ) -> "ClientWebSocketResponse[Literal[False]]": ...
1137 @overload
1138 async def _ws_connect(
1139 self,
1140 url: StrOrURL,
1141 *,
1142 decode_text: bool = ...,
1143 **kwargs: Unpack[_WSConnectOptions],
1144 ) -> "ClientWebSocketResponse[bool]": ...
1146 async def _ws_connect(
1147 self,
1148 url: StrOrURL,
1149 *,
1150 method: str = hdrs.METH_GET,
1151 protocols: Iterable[str] = (),
1152 timeout: ClientWSTimeout | _SENTINEL = sentinel,
1153 receive_timeout: float | None = None,
1154 autoclose: bool = True,
1155 autoping: bool = True,
1156 heartbeat: float | None = None,
1157 auth: BasicAuth | None = None,
1158 origin: str | None = None,
1159 params: Query = None,
1160 headers: LooseHeaders | None = None,
1161 proxy: StrOrURL | None = None,
1162 proxy_auth: BasicAuth | None = None,
1163 ssl: SSLContext | bool | Fingerprint = True,
1164 verify_ssl: bool | None = None,
1165 fingerprint: bytes | None = None,
1166 ssl_context: SSLContext | None = None,
1167 server_hostname: str | None = None,
1168 proxy_headers: LooseHeaders | None = None,
1169 compress: int = 0,
1170 max_msg_size: int = 4 * 1024 * 1024,
1171 decode_text: bool = True,
1172 ) -> "ClientWebSocketResponse[bool]":
1173 if auth is not None:
1174 warnings.warn(
1175 "The 'auth' parameter is deprecated and will be removed in v4;"
1176 " pass headers={'Authorization': "
1177 "aiohttp.encode_basic_auth(login, password)} instead",
1178 DeprecationWarning,
1179 stacklevel=3,
1180 )
1181 if proxy_auth is not None:
1182 warnings.warn(
1183 "The 'proxy_auth' parameter is deprecated and will be removed in v4;"
1184 " pass proxy_headers={'Proxy-Authorization': "
1185 "aiohttp.encode_basic_auth(login, password)} instead",
1186 DeprecationWarning,
1187 stacklevel=3,
1188 )
1189 if timeout is not sentinel:
1190 if isinstance(timeout, ClientWSTimeout):
1191 ws_timeout = timeout
1192 else:
1193 warnings.warn(
1194 "parameter 'timeout' of type 'float' "
1195 "is deprecated, please use "
1196 "'timeout=ClientWSTimeout(ws_close=...)'",
1197 DeprecationWarning,
1198 stacklevel=2,
1199 )
1200 ws_timeout = ClientWSTimeout(ws_close=timeout)
1201 else:
1202 ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT
1203 if receive_timeout is not None:
1204 warnings.warn(
1205 "float parameter 'receive_timeout' "
1206 "is deprecated, please use parameter "
1207 "'timeout=ClientWSTimeout(ws_receive=...)'",
1208 DeprecationWarning,
1209 stacklevel=2,
1210 )
1211 ws_timeout = attr.evolve(ws_timeout, ws_receive=receive_timeout)
1213 if headers is None:
1214 real_headers: CIMultiDict[str] = CIMultiDict()
1215 else:
1216 real_headers = CIMultiDict(headers)
1218 default_headers = {
1219 hdrs.UPGRADE: "websocket",
1220 hdrs.CONNECTION: "Upgrade",
1221 hdrs.SEC_WEBSOCKET_VERSION: "13",
1222 }
1224 for key, value in default_headers.items():
1225 real_headers.setdefault(key, value)
1227 sec_key = base64.b64encode(os.urandom(16))
1228 real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()
1230 if protocols:
1231 real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols)
1232 if origin is not None:
1233 real_headers[hdrs.ORIGIN] = origin
1234 if compress:
1235 extstr = ws_ext_gen(compress=compress)
1236 real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr
1238 # For the sake of backward compatibility, if user passes in None, convert it to True
1239 if ssl is None:
1240 warnings.warn(
1241 "ssl=None is deprecated, please use ssl=True",
1242 DeprecationWarning,
1243 stacklevel=2,
1244 )
1245 ssl = True
1246 ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
1248 # send request
1249 resp = await self.request(
1250 method,
1251 url,
1252 params=params,
1253 headers=real_headers,
1254 read_until_eof=False,
1255 auth=auth,
1256 proxy=proxy,
1257 proxy_auth=proxy_auth,
1258 ssl=ssl,
1259 server_hostname=server_hostname,
1260 proxy_headers=proxy_headers,
1261 )
1263 try:
1264 # check handshake
1265 if resp.status != 101:
1266 raise WSServerHandshakeError(
1267 resp.request_info,
1268 resp.history,
1269 message="Invalid response status",
1270 status=resp.status,
1271 headers=resp.headers,
1272 )
1274 if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket":
1275 raise WSServerHandshakeError(
1276 resp.request_info,
1277 resp.history,
1278 message="Invalid upgrade header",
1279 status=resp.status,
1280 headers=resp.headers,
1281 )
1283 if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade":
1284 raise WSServerHandshakeError(
1285 resp.request_info,
1286 resp.history,
1287 message="Invalid connection header",
1288 status=resp.status,
1289 headers=resp.headers,
1290 )
1292 # key calculation
1293 r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")
1294 match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
1295 if r_key != match:
1296 raise WSServerHandshakeError(
1297 resp.request_info,
1298 resp.history,
1299 message="Invalid challenge response",
1300 status=resp.status,
1301 headers=resp.headers,
1302 )
1304 # websocket protocol
1305 protocol = None
1306 if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:
1307 resp_protocols = [
1308 proto.strip()
1309 for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
1310 ]
1312 for proto in resp_protocols:
1313 if proto in protocols:
1314 protocol = proto
1315 break
1317 # websocket compress
1318 notakeover = False
1319 if compress:
1320 compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)
1321 if compress_hdrs:
1322 try:
1323 compress, notakeover = ws_ext_parse(compress_hdrs)
1324 except WSHandshakeError as exc:
1325 raise WSServerHandshakeError(
1326 resp.request_info,
1327 resp.history,
1328 message=exc.args[0],
1329 status=resp.status,
1330 headers=resp.headers,
1331 ) from exc
1332 else:
1333 compress = 0
1334 notakeover = False
1336 conn = resp.connection
1337 assert conn is not None
1338 conn_proto = conn.protocol
1339 assert conn_proto is not None
1341 # For WS connection the read_timeout must be either receive_timeout or greater
1342 # None == no timeout, i.e. infinite timeout, so None is the max timeout possible
1343 if ws_timeout.ws_receive is None:
1344 # Reset regardless
1345 conn_proto.read_timeout = None
1346 elif conn_proto.read_timeout is not None:
1347 conn_proto.read_timeout = max(
1348 ws_timeout.ws_receive, conn_proto.read_timeout
1349 )
1351 transport = conn.transport
1352 assert transport is not None
1353 reader = WebSocketDataQueue(conn_proto, DEFAULT_CHUNK_SIZE, loop=self._loop)
1354 writer = WebSocketWriter(
1355 conn_proto,
1356 transport,
1357 use_mask=True,
1358 compress=compress,
1359 notakeover=notakeover,
1360 )
1361 except BaseException:
1362 resp.close()
1363 raise
1364 else:
1365 ws_resp = self._ws_response_class(
1366 reader,
1367 writer,
1368 protocol,
1369 resp,
1370 ws_timeout,
1371 autoclose,
1372 autoping,
1373 self._loop,
1374 heartbeat=heartbeat,
1375 compress=compress,
1376 client_notakeover=notakeover,
1377 )
1378 parser = WebSocketReader(
1379 reader,
1380 max_msg_size,
1381 compress=bool(compress),
1382 decode_text=decode_text,
1383 )
1384 cb = None if heartbeat is None else ws_resp._on_data_received
1385 conn_proto.set_parser(parser, reader, data_received_cb=cb)
1386 return ws_resp
1388 def _prepare_headers(self, headers: LooseHeaders | None) -> "CIMultiDict[str]":
1389 """Add default headers and transform it to CIMultiDict"""
1390 # Convert headers to MultiDict
1391 result = CIMultiDict(self._default_headers)
1392 if headers:
1393 if not isinstance(headers, (MultiDictProxy, MultiDict)):
1394 headers = CIMultiDict(headers)
1395 added_names: set[str] = set()
1396 for key, value in headers.items():
1397 if key in added_names:
1398 result.add(key, value)
1399 else:
1400 result[key] = value
1401 added_names.add(key)
1402 return result
1404 def _get_netrc_auth(self, host: str) -> BasicAuth | None:
1405 """
1406 Get auth from netrc for the given host.
1408 This method is designed to be called in an executor to avoid
1409 blocking I/O in the event loop.
1410 """
1411 netrc_obj = netrc_from_env()
1412 try:
1413 return basicauth_from_netrc(netrc_obj, host)
1414 except LookupError:
1415 return None
1417 if sys.version_info >= (3, 11) and TYPE_CHECKING:
1419 def get(
1420 self,
1421 url: StrOrURL,
1422 **kwargs: Unpack[_RequestOptions],
1423 ) -> "_RequestContextManager": ...
1425 def options(
1426 self,
1427 url: StrOrURL,
1428 **kwargs: Unpack[_RequestOptions],
1429 ) -> "_RequestContextManager": ...
1431 def head(
1432 self,
1433 url: StrOrURL,
1434 **kwargs: Unpack[_RequestOptions],
1435 ) -> "_RequestContextManager": ...
1437 def post(
1438 self,
1439 url: StrOrURL,
1440 **kwargs: Unpack[_RequestOptions],
1441 ) -> "_RequestContextManager": ...
1443 def put(
1444 self,
1445 url: StrOrURL,
1446 **kwargs: Unpack[_RequestOptions],
1447 ) -> "_RequestContextManager": ...
1449 def patch(
1450 self,
1451 url: StrOrURL,
1452 **kwargs: Unpack[_RequestOptions],
1453 ) -> "_RequestContextManager": ...
1455 def delete(
1456 self,
1457 url: StrOrURL,
1458 **kwargs: Unpack[_RequestOptions],
1459 ) -> "_RequestContextManager": ...
1461 else:
1463 def get(
1464 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
1465 ) -> "_RequestContextManager":
1466 """Perform HTTP GET request."""
1467 return _RequestContextManager(
1468 self._request(
1469 hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs
1470 )
1471 )
1473 def options(
1474 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
1475 ) -> "_RequestContextManager":
1476 """Perform HTTP OPTIONS request."""
1477 return _RequestContextManager(
1478 self._request(
1479 hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs
1480 )
1481 )
1483 def head(
1484 self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any
1485 ) -> "_RequestContextManager":
1486 """Perform HTTP HEAD request."""
1487 return _RequestContextManager(
1488 self._request(
1489 hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs
1490 )
1491 )
1493 def post(
1494 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1495 ) -> "_RequestContextManager":
1496 """Perform HTTP POST request."""
1497 return _RequestContextManager(
1498 self._request(hdrs.METH_POST, url, data=data, **kwargs)
1499 )
1501 def put(
1502 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1503 ) -> "_RequestContextManager":
1504 """Perform HTTP PUT request."""
1505 return _RequestContextManager(
1506 self._request(hdrs.METH_PUT, url, data=data, **kwargs)
1507 )
1509 def patch(
1510 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1511 ) -> "_RequestContextManager":
1512 """Perform HTTP PATCH request."""
1513 return _RequestContextManager(
1514 self._request(hdrs.METH_PATCH, url, data=data, **kwargs)
1515 )
1517 def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager":
1518 """Perform HTTP DELETE request."""
1519 return _RequestContextManager(
1520 self._request(hdrs.METH_DELETE, url, **kwargs)
1521 )
1523 async def close(self) -> None:
1524 """Close underlying connector.
1526 Release all acquired resources.
1527 """
1528 if not self.closed:
1529 if self._connector is not None and self._connector_owner:
1530 await self._connector.close()
1531 self._connector = None
1533 @property
1534 def closed(self) -> bool:
1535 """Is client session closed.
1537 A readonly property.
1538 """
1539 return self._connector is None or self._connector.closed
1541 @property
1542 def connector(self) -> BaseConnector | None:
1543 """Connector instance used for the session."""
1544 return self._connector
1546 @property
1547 def cookie_jar(self) -> AbstractCookieJar:
1548 """The session cookies."""
1549 return self._cookie_jar
1551 @property
1552 def version(self) -> tuple[int, int]:
1553 """The session HTTP protocol version."""
1554 return self._version
1556 @property
1557 def requote_redirect_url(self) -> bool:
1558 """Do URL requoting on redirection handling."""
1559 return self._requote_redirect_url
1561 @requote_redirect_url.setter
1562 def requote_redirect_url(self, val: bool) -> None:
1563 """Do URL requoting on redirection handling."""
1564 warnings.warn(
1565 "session.requote_redirect_url modification is deprecated #2778",
1566 DeprecationWarning,
1567 stacklevel=2,
1568 )
1569 self._requote_redirect_url = val
1571 @property
1572 def loop(self) -> asyncio.AbstractEventLoop:
1573 """Session's loop."""
1574 warnings.warn(
1575 "client.loop property is deprecated", DeprecationWarning, stacklevel=2
1576 )
1577 return self._loop
1579 @property
1580 def timeout(self) -> ClientTimeout:
1581 """Timeout for the session."""
1582 return self._timeout
1584 @property
1585 def headers(self) -> "CIMultiDict[str]":
1586 """The default headers of the client session."""
1587 return self._default_headers
1589 @property
1590 def skip_auto_headers(self) -> frozenset[istr]:
1591 """Headers for which autogeneration should be skipped"""
1592 return self._skip_auto_headers
1594 @property
1595 def auth(self) -> BasicAuth | None:
1596 """An object that represents HTTP Basic Authorization"""
1597 return self._default_auth
1599 @property
1600 def json_serialize(self) -> JSONEncoder:
1601 """Json serializer callable"""
1602 return self._json_serialize
1604 @property
1605 def connector_owner(self) -> bool:
1606 """Should connector be closed on session closing"""
1607 return self._connector_owner
1609 @property
1610 def raise_for_status(
1611 self,
1612 ) -> bool | Callable[[ClientResponse], Awaitable[None]]:
1613 """Should `ClientResponse.raise_for_status()` be called for each response."""
1614 return self._raise_for_status
1616 @property
1617 def auto_decompress(self) -> bool:
1618 """Should the body response be automatically decompressed."""
1619 return self._auto_decompress
1621 @property
1622 def trust_env(self) -> bool:
1623 """
1624 Should proxies information from environment or netrc be trusted.
1626 Information is from HTTP_PROXY / HTTPS_PROXY environment variables
1627 or ~/.netrc file if present.
1628 """
1629 return self._trust_env
1631 @property
1632 def trace_configs(self) -> list[TraceConfig]:
1633 """A list of TraceConfig instances used for client tracing"""
1634 return self._trace_configs
1636 def detach(self) -> None:
1637 """Detach connector from session without closing the former.
1639 Session is switched to closed state anyway.
1640 """
1641 self._connector = None
1643 def __enter__(self) -> None:
1644 raise TypeError("Use async with instead")
1646 def __exit__(
1647 self,
1648 exc_type: type[BaseException] | None,
1649 exc_val: BaseException | None,
1650 exc_tb: TracebackType | None,
1651 ) -> None:
1652 # __exit__ should exist in pair with __enter__ but never executed
1653 pass # pragma: no cover
1655 async def __aenter__(self) -> "ClientSession":
1656 return self
1658 async def __aexit__(
1659 self,
1660 exc_type: type[BaseException] | None,
1661 exc_val: BaseException | None,
1662 exc_tb: TracebackType | None,
1663 ) -> None:
1664 await self.close()
1667class _BaseRequestContextManager(
1668 Coroutine[Any, Any, _RetType_co], Generic[_RetType_co]
1669):
1671 __slots__ = ("_coro", "_resp")
1673 def __init__(self, coro: Coroutine[asyncio.Future[Any], None, _RetType_co]) -> None:
1674 self._coro: Coroutine[asyncio.Future[Any], None, _RetType_co] = coro
1676 def send(self, arg: None) -> asyncio.Future[Any]:
1677 return self._coro.send(arg)
1679 def throw(self, *args: Any, **kwargs: Any) -> asyncio.Future[Any]:
1680 return self._coro.throw(*args, **kwargs)
1682 def close(self) -> None:
1683 return self._coro.close()
1685 def __await__(self) -> Generator[Any, None, _RetType_co]:
1686 ret = self._coro.__await__()
1687 return ret
1689 def __iter__(self) -> Generator[Any, None, _RetType_co]:
1690 return self.__await__()
1692 async def __aenter__(self) -> _RetType_co:
1693 self._resp: _RetType_co = await self._coro
1694 return await self._resp.__aenter__() # type: ignore[return-value]
1696 async def __aexit__(
1697 self,
1698 exc_type: type[BaseException] | None,
1699 exc: BaseException | None,
1700 tb: TracebackType | None,
1701 ) -> None:
1702 await self._resp.__aexit__(exc_type, exc, tb)
1705_RequestContextManager = _BaseRequestContextManager[ClientResponse]
1706_WSRequestContextManager = _BaseRequestContextManager[ClientWebSocketResponse[bool]]
1709class _SessionRequestContextManager:
1711 __slots__ = ("_coro", "_resp", "_session")
1713 def __init__(
1714 self,
1715 coro: Coroutine[asyncio.Future[Any], None, ClientResponse],
1716 session: ClientSession,
1717 ) -> None:
1718 self._coro = coro
1719 self._resp: ClientResponse | None = None
1720 self._session = session
1722 async def __aenter__(self) -> ClientResponse:
1723 try:
1724 self._resp = await self._coro
1725 except BaseException:
1726 await self._session.close()
1727 raise
1728 else:
1729 return self._resp
1731 async def __aexit__(
1732 self,
1733 exc_type: type[BaseException] | None,
1734 exc: BaseException | None,
1735 tb: TracebackType | None,
1736 ) -> None:
1737 assert self._resp is not None
1738 self._resp.close()
1739 await self._session.close()
1742if sys.version_info >= (3, 11) and TYPE_CHECKING:
1744 def request(
1745 method: str,
1746 url: StrOrURL,
1747 *,
1748 version: HttpVersion = http.HttpVersion11,
1749 connector: BaseConnector | None = None,
1750 loop: asyncio.AbstractEventLoop | None = None,
1751 **kwargs: Unpack[_RequestOptions],
1752 ) -> _SessionRequestContextManager: ...
1754else:
1756 def request(
1757 method: str,
1758 url: StrOrURL,
1759 *,
1760 version: HttpVersion = http.HttpVersion11,
1761 connector: BaseConnector | None = None,
1762 loop: asyncio.AbstractEventLoop | None = None,
1763 **kwargs: Any,
1764 ) -> _SessionRequestContextManager:
1765 """Constructs and sends a request.
1767 Returns response object.
1768 method - HTTP method
1769 url - request url
1770 params - (optional) Dictionary or bytes to be sent in the query
1771 string of the new request
1772 data - (optional) Dictionary, bytes, or file-like object to
1773 send in the body of the request
1774 json - (optional) Any json compatible python object
1775 headers - (optional) Dictionary of HTTP Headers to send with
1776 the request
1777 cookies - (optional) Dict object to send with the request
1778 auth - (optional) BasicAuth named tuple represent HTTP Basic Auth
1779 auth - aiohttp.helpers.BasicAuth
1780 allow_redirects - (optional) If set to False, do not follow
1781 redirects
1782 version - Request HTTP version.
1783 compress - Set to True if request has to be compressed
1784 with deflate encoding.
1785 chunked - Set to chunk size for chunked transfer encoding.
1786 expect100 - Expect 100-continue response from server.
1787 connector - BaseConnector sub-class instance to support
1788 connection pooling.
1789 read_until_eof - Read response until eof if response
1790 does not have Content-Length header.
1791 loop - Optional event loop.
1792 timeout - Optional ClientTimeout settings structure, 5min
1793 total timeout by default.
1794 Usage::
1795 >>> import aiohttp
1796 >>> async with aiohttp.request('GET', 'http://python.org/') as resp:
1797 ... print(resp)
1798 ... data = await resp.read()
1799 <ClientResponse(https://www.python.org/) [200 OK]>
1800 """
1801 connector_owner = False
1802 if connector is None:
1803 connector_owner = True
1804 connector = TCPConnector(loop=loop, force_close=True)
1806 session = ClientSession(
1807 loop=loop,
1808 cookies=kwargs.pop("cookies", None),
1809 version=version,
1810 timeout=kwargs.pop("timeout", sentinel),
1811 connector=connector,
1812 connector_owner=connector_owner,
1813 )
1815 return _SessionRequestContextManager(
1816 session._request(method, url, **kwargs),
1817 session,
1818 )