Coverage for /pythoncovmergedfiles/medio/medio/src/aiohttp/aiohttp/client.py: 28%

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

668 statements  

1"""HTTP Client for asyncio.""" 

2 

3import asyncio 

4import base64 

5import dataclasses 

6import hashlib 

7import json 

8import os 

9import sys 

10import traceback 

11import warnings 

12from collections.abc import ( 

13 Awaitable, 

14 Callable, 

15 Collection, 

16 Coroutine, 

17 Generator, 

18 Iterable, 

19 Sequence, 

20) 

21from contextlib import suppress 

22from types import TracebackType 

23from typing import ( 

24 TYPE_CHECKING, 

25 Any, 

26 Generic, 

27 Literal, 

28 TypedDict, 

29 TypeVar, 

30 Unpack, 

31 final, 

32 overload, 

33) 

34 

35from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr 

36from yarl import URL, Query 

37 

38from . import hdrs, http, payload 

39from ._websocket.reader import WebSocketDataQueue 

40from .abc import AbstractCookieJar 

41from .client_exceptions import ( 

42 ClientConnectionError, 

43 ClientConnectionResetError, 

44 ClientConnectorCertificateError, 

45 ClientConnectorDNSError, 

46 ClientConnectorError, 

47 ClientConnectorSSLError, 

48 ClientError, 

49 ClientHttpProxyError, 

50 ClientOSError, 

51 ClientPayloadError, 

52 ClientProxyConnectionError, 

53 ClientResponseError, 

54 ClientSSLError, 

55 ConnectionTimeoutError, 

56 ContentTypeError, 

57 InvalidURL, 

58 InvalidUrlClientError, 

59 InvalidUrlRedirectClientError, 

60 NonHttpUrlClientError, 

61 NonHttpUrlRedirectClientError, 

62 RedirectClientError, 

63 ServerConnectionError, 

64 ServerDisconnectedError, 

65 ServerFingerprintMismatch, 

66 ServerTimeoutError, 

67 SocketTimeoutError, 

68 TooManyRedirects, 

69 UploadAbortedError, 

70 WSMessageTypeError, 

71 WSServerHandshakeError, 

72) 

73from .client_middlewares import ClientMiddlewareType, _cached_build_client_middlewares 

74from .client_reqrep import ( 

75 SSL_ALLOWED_TYPES, 

76 ClientRequest, 

77 ClientResponse, 

78 ClientTimeout, 

79 Fingerprint, 

80 RequestInfo, 

81 ResponseParams, 

82 UploadTracker, 

83) 

84from .client_ws import ( 

85 DEFAULT_WS_CLIENT_TIMEOUT, 

86 ClientWebSocketResponse, 

87 ClientWSTimeout, 

88) 

89from .connector import BaseConnector, NamedPipeConnector, TCPConnector, UnixConnector 

90from .cookiejar import CookieJar, _UnlimitedCookieJar 

91from .helpers import ( 

92 _SENTINEL, 

93 DEFAULT_CHUNK_SIZE, 

94 EMPTY_BODY_METHODS, 

95 HTTP_AND_EMPTY_SCHEMA_SET, 

96 TimeoutHandle, 

97 _auth_header_from_netrc, 

98 get_env_proxy_for_url, 

99 netrc_from_env, 

100 sentinel, 

101 strip_auth_from_url, 

102) 

103from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter 

104from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse 

105from .tracing import Trace, TraceConfig 

106from .typedefs import ( 

107 JSONBytesEncoder, 

108 JSONEncoder, 

109 LooseCookies, 

110 LooseHeaders, 

111 StrOrURL, 

112) 

113 

114__all__ = ( 

115 # client_exceptions 

116 "ClientConnectionError", 

117 "ClientConnectionResetError", 

118 "ClientConnectorCertificateError", 

119 "ClientConnectorDNSError", 

120 "ClientConnectorError", 

121 "ClientConnectorSSLError", 

122 "ClientError", 

123 "ClientHttpProxyError", 

124 "ClientOSError", 

125 "ClientPayloadError", 

126 "ClientProxyConnectionError", 

127 "ClientResponseError", 

128 "ClientSSLError", 

129 "ConnectionTimeoutError", 

130 "ContentTypeError", 

131 "InvalidURL", 

132 "InvalidUrlClientError", 

133 "RedirectClientError", 

134 "NonHttpUrlClientError", 

135 "InvalidUrlRedirectClientError", 

136 "NonHttpUrlRedirectClientError", 

137 "ServerConnectionError", 

138 "ServerDisconnectedError", 

139 "ServerFingerprintMismatch", 

140 "ServerTimeoutError", 

141 "SocketTimeoutError", 

142 "TooManyRedirects", 

143 "UploadAbortedError", 

144 "WSServerHandshakeError", 

145 # client_reqrep 

146 "ClientRequest", 

147 "ClientResponse", 

148 "Fingerprint", 

149 "RequestInfo", 

150 "UploadTracker", 

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) 

165 

166 

167if TYPE_CHECKING: 

168 from ssl import SSLContext 

169else: 

170 SSLContext = None 

171 

172# URL parsers strip leading and trailing C0 control characters and spaces and 

173# drop tabs and newlines before splitting a URL. 

174_C0_CONTROL_OR_SPACE = "".join(map(chr, range(0x21))) 

175_REMOVE_TAB_OR_NEWLINE = str.maketrans("", "", "\t\n\r") 

176 

177 

178def _has_no_authority(location: str, scheme: str) -> bool: 

179 """Tell if a URL with a scheme has no "//" after the scheme. 

180 

181 Browsers read a backslash like a slash there for http and https. 

182 """ 

183 location = location.strip(_C0_CONTROL_OR_SPACE).translate(_REMOVE_TAB_OR_NEWLINE) 

184 rest = location[len(scheme) + 1 : len(scheme) + 3] 

185 return rest[:1] not in ("/", "\\") or rest[1:] not in ("/", "\\") 

186 

187 

188class _RequestOptions(TypedDict, total=False): 

189 params: Query 

190 data: Any 

191 json: Any 

192 cookies: LooseCookies | None 

193 headers: LooseHeaders | None 

194 skip_auto_headers: Iterable[str] | None 

195 allow_redirects: bool 

196 max_redirects: int 

197 compress: Literal["deflate", "gzip"] | bool 

198 chunked: bool | None 

199 expect100: bool 

200 raise_for_status: None | bool | Callable[[ClientResponse], Awaitable[None]] 

201 read_until_eof: bool 

202 proxy: StrOrURL | None 

203 timeout: "ClientTimeout | _SENTINEL | None" 

204 ssl: SSLContext | bool | Fingerprint | _SENTINEL 

205 server_hostname: str | None 

206 proxy_headers: LooseHeaders | None 

207 trace_request_ctx: object 

208 read_bufsize: int | None 

209 auto_decompress: bool | None 

210 max_line_size: int | None 

211 max_field_size: int | None 

212 max_headers: int | None 

213 middlewares: Sequence[ClientMiddlewareType] | None 

214 upload_tracker: UploadTracker | None 

215 

216 

217class _WSConnectOptions(TypedDict, total=False): 

218 method: str 

219 protocols: Collection[str] 

220 timeout: "ClientWSTimeout | _SENTINEL" 

221 receive_timeout: float | None 

222 autoclose: bool 

223 autoping: bool 

224 heartbeat: float | None 

225 origin: str | None 

226 params: Query 

227 headers: LooseHeaders | None 

228 proxy: StrOrURL | None 

229 ssl: SSLContext | bool | Fingerprint | _SENTINEL 

230 server_hostname: str | None 

231 proxy_headers: LooseHeaders | None 

232 compress: int 

233 max_msg_size: int 

234 

235 

236# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2 

237# https://www.rfc-editor.org/info/rfc10008/#section-1-12 

238IDEMPOTENT_METHODS = frozenset( 

239 {"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE", "QUERY"} 

240) 

241 

242_RetType_co = TypeVar( 

243 "_RetType_co", 

244 bound="ClientResponse | ClientWebSocketResponse[bool]", 

245 covariant=True, 

246) 

247_CharsetResolver = Callable[[ClientResponse, bytes], str] 

248 

249 

250class _SessionOptions(TypedDict, total=False): 

251 base_url: StrOrURL | None 

252 connector: BaseConnector | None 

253 cookies: LooseCookies | None 

254 headers: LooseHeaders | None 

255 proxy: StrOrURL | None 

256 ssl: SSLContext | bool | Fingerprint 

257 skip_auto_headers: Iterable[str] | None 

258 json_serialize: JSONEncoder 

259 json_serialize_bytes: JSONBytesEncoder | None 

260 request_class: type[ClientRequest] 

261 response_class: type[ClientResponse] 

262 ws_response_class: type[ClientWebSocketResponse] 

263 version: HttpVersion 

264 connector_owner: bool 

265 raise_for_status: bool | Callable[[ClientResponse], Awaitable[None]] 

266 timeout: "_SENTINEL | ClientTimeout | None" 

267 auto_decompress: bool 

268 trust_env: bool 

269 requote_redirect_url: bool 

270 trace_configs: list[TraceConfig[object]] | None 

271 read_bufsize: int 

272 max_line_size: int 

273 max_field_size: int 

274 max_headers: int 

275 fallback_charset_resolver: _CharsetResolver 

276 middlewares: Sequence[ClientMiddlewareType] 

277 ssl_shutdown_timeout: _SENTINEL | None | float 

278 

279 

280# Module-level (not a closure) so it has a stable identity for the 

281# ``_cached_build_client_middlewares`` cache key. 

282async def _connect_and_send_request(req: ClientRequest) -> ClientResponse: 

283 connector = req._session._connector 

284 assert connector is not None 

285 if (tracker := req._upload_tracker) is not None: 

286 req._upload_gen = tracker._attempt_started() 

287 try: 

288 conn = await connector.connect(req, traces=req._traces, timeout=req._timeout) 

289 except TimeoutError as exc: 

290 raise ConnectionTimeoutError(f"Connection timeout to host {req.url}") from exc 

291 

292 assert conn.protocol is not None 

293 conn.protocol.set_response_params(**req._response_params) 

294 try: 

295 resp = await req._send(conn) 

296 try: 

297 await resp.start(conn) 

298 except BaseException: 

299 resp.close() 

300 raise 

301 except BaseException: 

302 conn.close() 

303 raise 

304 return resp 

305 

306 

307@final 

308class ClientSession: 

309 """First-class interface for making HTTP requests.""" 

310 

311 __slots__ = ( 

312 "_base_url", 

313 "_base_url_origin", 

314 "_source_traceback", 

315 "_connector", 

316 "_loop", 

317 "_cookie_jar", 

318 "_connector_owner", 

319 "_version", 

320 "_json_serialize", 

321 "_json_serialize_bytes", 

322 "_requote_redirect_url", 

323 "_timeout", 

324 "_raise_for_status", 

325 "_auto_decompress", 

326 "_trust_env", 

327 "_default_headers", 

328 "_skip_auto_headers", 

329 "_request_class", 

330 "_response_class", 

331 "_ws_response_class", 

332 "_trace_configs", 

333 "_read_bufsize", 

334 "_max_line_size", 

335 "_max_field_size", 

336 "_max_headers", 

337 "_resolve_charset", 

338 "_default_proxy", 

339 "_default_ssl", 

340 "_retry_connection", 

341 "_middlewares", 

342 ) 

343 

344 def __init__( 

345 self, 

346 base_url: StrOrURL | None = None, 

347 *, 

348 connector: BaseConnector | None = None, 

349 cookies: LooseCookies | None = None, 

350 headers: LooseHeaders | None = None, 

351 proxy: StrOrURL | None = None, 

352 ssl: SSLContext | bool | Fingerprint = True, 

353 skip_auto_headers: Iterable[str] | None = None, 

354 json_serialize: JSONEncoder = json.dumps, 

355 json_serialize_bytes: JSONBytesEncoder | None = None, 

356 request_class: type[ClientRequest] = ClientRequest, 

357 response_class: type[ClientResponse] = ClientResponse, 

358 ws_response_class: type[ClientWebSocketResponse] = ClientWebSocketResponse, 

359 version: HttpVersion = http.HttpVersion11, 

360 cookie_jar: AbstractCookieJar | None = None, 

361 connector_owner: bool = True, 

362 raise_for_status: bool | Callable[[ClientResponse], Awaitable[None]] = False, 

363 timeout: _SENTINEL | ClientTimeout | None = sentinel, 

364 auto_decompress: bool = True, 

365 trust_env: bool = False, 

366 requote_redirect_url: bool = True, 

367 trace_configs: list[TraceConfig[object]] | None = None, 

368 read_bufsize: int = DEFAULT_CHUNK_SIZE, 

369 max_line_size: int = 8190, 

370 max_field_size: int = 8190, 

371 max_headers: int = 128, 

372 fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8", 

373 middlewares: Sequence[ClientMiddlewareType] = (), 

374 ssl_shutdown_timeout: _SENTINEL | None | float = sentinel, 

375 ) -> None: 

376 # We initialise _connector to None immediately, as it's referenced in __del__() 

377 # and could cause issues if an exception occurs during initialisation. 

378 self._connector: BaseConnector | None = None 

379 if base_url is None or isinstance(base_url, URL): 

380 self._base_url: URL | None = base_url 

381 self._base_url_origin = None if base_url is None else base_url.origin() 

382 else: 

383 self._base_url = URL(base_url) 

384 self._base_url_origin = self._base_url.origin() 

385 assert self._base_url.absolute, "Only absolute URLs are supported" 

386 if self._base_url is not None and not self._base_url.path.endswith("/"): 

387 raise ValueError("base_url must have a trailing '/'") 

388 

389 if not isinstance(ssl, SSL_ALLOWED_TYPES): 

390 raise TypeError( 

391 "ssl should be SSLContext, Fingerprint, or bool, " 

392 f"got {ssl!r} instead." 

393 ) 

394 

395 loop = asyncio.get_running_loop() 

396 

397 if timeout is sentinel or timeout is None: 

398 timeout = ClientTimeout() 

399 if not isinstance(timeout, ClientTimeout): 

400 raise ValueError( 

401 f"timeout parameter cannot be of {type(timeout)} type, " 

402 "please use 'timeout=ClientTimeout(...)'", 

403 ) 

404 self._timeout = timeout 

405 

406 if ssl_shutdown_timeout is not sentinel: 

407 warnings.warn( 

408 "The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0", 

409 DeprecationWarning, 

410 stacklevel=2, 

411 ) 

412 

413 if connector is None: 

414 connector = TCPConnector(ssl_shutdown_timeout=ssl_shutdown_timeout) 

415 # Initialize these three attrs before raising any exception, 

416 # they are used in __del__ 

417 self._connector = connector 

418 self._loop = loop 

419 if loop.get_debug(): 

420 self._source_traceback: traceback.StackSummary | None = ( 

421 traceback.extract_stack(sys._getframe(1)) 

422 ) 

423 else: 

424 self._source_traceback = None 

425 

426 if connector._loop is not loop: 

427 raise RuntimeError("Session and connector have to use same event loop") 

428 

429 if cookie_jar is None: 

430 cookie_jar = CookieJar() 

431 self._cookie_jar = cookie_jar 

432 

433 if cookies: 

434 self._cookie_jar.update_cookies(cookies) 

435 

436 self._connector_owner = connector_owner 

437 self._version = version 

438 self._json_serialize = json_serialize 

439 self._json_serialize_bytes = json_serialize_bytes 

440 self._raise_for_status = raise_for_status 

441 self._auto_decompress = auto_decompress 

442 self._trust_env = trust_env 

443 self._requote_redirect_url = requote_redirect_url 

444 self._read_bufsize = read_bufsize 

445 self._max_line_size = max_line_size 

446 self._max_field_size = max_field_size 

447 self._max_headers = max_headers 

448 

449 # Convert to list of tuples 

450 if headers: 

451 real_headers: CIMultiDict[str] = CIMultiDict(headers) 

452 else: 

453 real_headers = CIMultiDict() 

454 self._default_headers: CIMultiDict[str] = real_headers 

455 if skip_auto_headers is not None: 

456 self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers) 

457 else: 

458 self._skip_auto_headers = frozenset() 

459 

460 self._request_class = request_class 

461 self._response_class = response_class 

462 self._ws_response_class = ws_response_class 

463 

464 self._trace_configs = trace_configs or [] 

465 for trace_config in self._trace_configs: 

466 trace_config.freeze() 

467 

468 self._resolve_charset = fallback_charset_resolver 

469 

470 self._default_proxy = proxy 

471 self._default_ssl = ssl 

472 self._retry_connection: bool = True 

473 self._middlewares = tuple(middlewares) 

474 

475 def __init_subclass__(cls: type["ClientSession"]) -> None: 

476 raise TypeError( 

477 f"Inheritance class {cls.__name__} from ClientSession is forbidden" 

478 ) 

479 

480 def __del__(self, _warnings: Any = warnings) -> None: 

481 if not self.closed: 

482 _warnings.warn( 

483 f"Unclosed client session {self!r}", 

484 ResourceWarning, 

485 source=self, 

486 ) 

487 context = {"client_session": self, "message": "Unclosed client session"} 

488 if self._source_traceback is not None: 

489 context["source_traceback"] = self._source_traceback 

490 self._loop.call_exception_handler(context) 

491 

492 if TYPE_CHECKING: 

493 

494 def request( 

495 self, 

496 method: str, 

497 url: StrOrURL, 

498 **kwargs: Unpack[_RequestOptions], 

499 ) -> "_RequestContextManager": ... 

500 

501 else: 

502 

503 def request( 

504 self, method: str, url: StrOrURL, **kwargs: Any 

505 ) -> "_RequestContextManager": 

506 """Perform HTTP request.""" 

507 return _RequestContextManager(self._request(method, url, **kwargs)) 

508 

509 def _build_url(self, str_or_url: StrOrURL) -> URL: 

510 url = URL(str_or_url) 

511 if self._base_url and not url.absolute: 

512 return self._base_url.join(url) 

513 return url 

514 

515 async def _request( 

516 self, 

517 method: str, 

518 str_or_url: StrOrURL, 

519 *, 

520 params: Query = None, 

521 data: Any = None, 

522 json: Any = None, 

523 cookies: LooseCookies | None = None, 

524 headers: LooseHeaders | None = None, 

525 skip_auto_headers: Iterable[str] | None = None, 

526 allow_redirects: bool = True, 

527 max_redirects: int = 10, 

528 compress: Literal["deflate", "gzip"] | bool = False, 

529 chunked: bool | None = None, 

530 expect100: bool = False, 

531 raise_for_status: ( 

532 None | bool | Callable[[ClientResponse], Awaitable[None]] 

533 ) = None, 

534 read_until_eof: bool = True, 

535 proxy: StrOrURL | None = None, 

536 timeout: ClientTimeout | _SENTINEL | None = sentinel, 

537 ssl: SSLContext | bool | Fingerprint | _SENTINEL = sentinel, 

538 server_hostname: str | None = None, 

539 proxy_headers: LooseHeaders | None = None, 

540 trace_request_ctx: object = None, 

541 read_bufsize: int | None = None, 

542 auto_decompress: bool | None = None, 

543 max_line_size: int | None = None, 

544 max_field_size: int | None = None, 

545 max_headers: int | None = None, 

546 middlewares: Sequence[ClientMiddlewareType] | None = None, 

547 upload_tracker: UploadTracker | None = None, 

548 ) -> ClientResponse: 

549 # NOTE: timeout clamps existing connect and read timeouts. We cannot 

550 # set the default to None because we need to detect if the user wants 

551 # to use the existing timeouts by setting timeout to None. 

552 

553 # Bound outside the settle-guaranteeing try block: a rebind error 

554 # must not settle a tracker owned by another request. 

555 if upload_tracker is not None: 

556 upload_tracker._bind() 

557 

558 tm: TimeoutHandle | None = None 

559 handle: asyncio.TimerHandle | None = None 

560 # Only traces that saw send_request_start; they must also see a terminal event. 

561 traces: list[Trace] = [] 

562 req: ClientRequest | None = None 

563 resp: ClientResponse | None = None 

564 try: 

565 if self.closed: 

566 raise RuntimeError("Session is closed") 

567 

568 method = method.upper() 

569 

570 if ssl is sentinel: 

571 ssl = self._default_ssl 

572 if not isinstance(ssl, SSL_ALLOWED_TYPES): 

573 raise TypeError( 

574 "ssl should be SSLContext, Fingerprint, or bool, " 

575 f"got {ssl!r} instead." 

576 ) 

577 

578 if data is not None and json is not None: 

579 raise ValueError( 

580 "data and json parameters can not be used at the same time" 

581 ) 

582 elif json is not None: 

583 if self._json_serialize_bytes is not None: 

584 data = payload.JsonBytesPayload( 

585 json, dumps=self._json_serialize_bytes 

586 ) 

587 else: 

588 data = payload.JsonPayload(json, dumps=self._json_serialize) 

589 

590 redirects = 0 

591 history: list[ClientResponse] = [] 

592 version = self._version 

593 params = params or {} 

594 

595 # Merge with default headers and transform to CIMultiDict 

596 headers = self._prepare_headers(headers) 

597 

598 try: 

599 url = self._build_url(str_or_url) 

600 except ValueError as e: 

601 raise InvalidUrlClientError(str_or_url) from e 

602 

603 assert self._connector is not None 

604 if url.scheme not in self._connector.allowed_protocol_schema_set: 

605 raise NonHttpUrlClientError(url) 

606 

607 skip_headers: Iterable[istr] | None 

608 if skip_auto_headers is not None: 

609 skip_headers = { 

610 istr(i) for i in skip_auto_headers 

611 } | self._skip_auto_headers 

612 elif self._skip_auto_headers: 

613 skip_headers = self._skip_auto_headers 

614 else: 

615 skip_headers = None 

616 

617 if proxy is None: 

618 proxy = self._default_proxy 

619 

620 resolved_proxy_headers: CIMultiDict[str] | None 

621 if proxy is None: 

622 resolved_proxy_headers = None 

623 else: 

624 resolved_proxy_headers = self._prepare_headers(proxy_headers) 

625 try: 

626 proxy = URL(proxy) 

627 except ValueError as e: 

628 raise InvalidURL(proxy) from e 

629 

630 real_timeout = ( 

631 self._timeout if timeout is sentinel or timeout is None else timeout 

632 ) 

633 # timeout is cumulative for all request operations 

634 # (request, redirects, responses, data consuming) 

635 tm = TimeoutHandle( 

636 self._loop, 

637 real_timeout.total, 

638 ceil_threshold=real_timeout.ceil_threshold, 

639 ) 

640 handle = tm.start() 

641 

642 if read_bufsize is None: 

643 read_bufsize = self._read_bufsize 

644 

645 if auto_decompress is None: 

646 auto_decompress = self._auto_decompress 

647 

648 if max_line_size is None: 

649 max_line_size = self._max_line_size 

650 

651 if max_field_size is None: 

652 max_field_size = self._max_field_size 

653 

654 if max_headers is None: 

655 max_headers = self._max_headers 

656 

657 for trace_config in self._trace_configs: 

658 trace = Trace( 

659 self, 

660 trace_config, 

661 trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx), 

662 ) 

663 await trace.send_request_start( 

664 method, url.update_query(params), headers 

665 ) 

666 traces.append(trace) 

667 

668 timer = tm.timer() 

669 with timer: 

670 # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests 

671 retry_persistent_connection = ( 

672 self._retry_connection and method in IDEMPOTENT_METHODS 

673 ) 

674 while True: 

675 url, auth_from_url = strip_auth_from_url(url) 

676 if not url.raw_host: 

677 # NOTE: Bail early, otherwise, causes `InvalidURL` through 

678 # NOTE: `self._request_class()` below. 

679 err_exc_cls = ( 

680 InvalidUrlRedirectClientError 

681 if redirects 

682 else InvalidUrlClientError 

683 ) 

684 raise err_exc_cls(url) 

685 

686 if auth_from_url is not None: 

687 # URL-embedded credentials override any Authorization 

688 # header already present (e.g. carried from a previous 

689 # redirect). On the initial request, refuse to silently 

690 # shadow an explicit Authorization header. 

691 if not history and hdrs.AUTHORIZATION in headers: 

692 raise ValueError( 

693 "Cannot combine AUTHORIZATION header with " 

694 "credentials encoded in URL" 

695 ) 

696 headers[hdrs.AUTHORIZATION] = auth_from_url 

697 elif ( 

698 self._trust_env 

699 and url.host is not None 

700 and hdrs.AUTHORIZATION not in headers 

701 ): 

702 # Fall back to ~/.netrc credentials when trust_env is set. 

703 netrc_auth = await self._loop.run_in_executor( 

704 None, self._get_netrc_auth, url.host 

705 ) 

706 if netrc_auth is not None: 

707 headers[hdrs.AUTHORIZATION] = netrc_auth 

708 

709 all_cookies = self._cookie_jar.filter_cookies(url) 

710 

711 if cookies is not None: 

712 tmp_cookie_jar = _UnlimitedCookieJar( 

713 unsafe=self._cookie_jar.unsafe, 

714 quote_cookie=self._cookie_jar.quote_cookie, 

715 treat_as_secure_origin=self._cookie_jar.treat_as_secure_origin, 

716 ) 

717 tmp_cookie_jar.update_cookies(cookies) 

718 req_cookies = tmp_cookie_jar.filter_cookies(url) 

719 if req_cookies: 

720 all_cookies.load(req_cookies) 

721 

722 proxy_: URL | None = None 

723 if proxy is not None: 

724 proxy_ = URL(proxy) 

725 elif self._trust_env: 

726 # Re-resolve per iteration; drop stale env-proxy auth so 

727 # a redirect that switches proxies can't leak credentials. 

728 resolved_proxy_headers = None 

729 with suppress(LookupError): 

730 proxy_, env_proxy_auth = await asyncio.to_thread( 

731 get_env_proxy_for_url, url 

732 ) 

733 if env_proxy_auth is not None: 

734 resolved_proxy_headers = CIMultiDict( 

735 {hdrs.PROXY_AUTHORIZATION: env_proxy_auth} 

736 ) 

737 

738 response_params: ResponseParams = { 

739 "timer": timer, 

740 "skip_payload": method in EMPTY_BODY_METHODS, 

741 "read_until_eof": read_until_eof, 

742 "auto_decompress": auto_decompress, 

743 "read_timeout": real_timeout.sock_read, 

744 "read_bufsize": read_bufsize, 

745 "timeout_ceil_threshold": self._connector._timeout_ceil_threshold, 

746 "max_line_size": max_line_size, 

747 "max_field_size": max_field_size, 

748 "max_headers": max_headers, 

749 } 

750 

751 req = self._request_class( 

752 method, 

753 url, 

754 params=params, 

755 headers=headers, 

756 skip_auto_headers=skip_headers, 

757 data=data, 

758 cookies=all_cookies, 

759 version=version, 

760 compress=compress, 

761 chunked=chunked, 

762 expect100=expect100, 

763 loop=self._loop, 

764 response_class=self._response_class, 

765 proxy=proxy_, 

766 response_params=response_params, 

767 timer=timer, 

768 timeout=real_timeout, 

769 session=self, 

770 ssl=ssl, 

771 server_hostname=server_hostname, 

772 proxy_headers=resolved_proxy_headers, 

773 traces=traces, 

774 trust_env=self.trust_env, 

775 ) 

776 req._upload_tracker = upload_tracker 

777 

778 # Apply middleware (if any) - per-request middleware overrides session middleware 

779 effective_middlewares = ( 

780 self._middlewares if middlewares is None else tuple(middlewares) 

781 ) 

782 

783 if effective_middlewares: 

784 handler = _cached_build_client_middlewares( 

785 _connect_and_send_request, effective_middlewares 

786 ) 

787 else: 

788 handler = _connect_and_send_request 

789 

790 try: 

791 resp = await handler(req) 

792 # Client connector errors should not be retried 

793 except ( 

794 ConnectionTimeoutError, 

795 ClientConnectorError, 

796 ClientConnectorCertificateError, 

797 ClientConnectorSSLError, 

798 ): 

799 raise 

800 except (ClientOSError, ServerDisconnectedError): 

801 if not retry_persistent_connection: 

802 raise 

803 retry_persistent_connection = False 

804 if data is not None: 

805 # Rebuilding from `data` would resend only the unread 

806 # remainder of a file object; reuse the payload, which 

807 # rewinds itself once the cancelled writer has settled. 

808 await req._close() 

809 if req._body.consumed: 

810 raise 

811 data = req._body 

812 continue 

813 except ClientError: 

814 raise 

815 except OSError as exc: 

816 if exc.errno is None and isinstance(exc, asyncio.TimeoutError): 

817 raise 

818 raise ClientOSError(*exc.args) from exc 

819 

820 # Update cookies from raw headers to preserve duplicates 

821 if resp._raw_cookie_headers: 

822 self._cookie_jar.update_cookies_from_headers( 

823 resp._raw_cookie_headers, resp.url 

824 ) 

825 

826 # redirects 

827 if resp.status in (301, 302, 303, 307, 308) and allow_redirects: 

828 for trace in traces: 

829 await trace.send_request_redirect( 

830 method, url.update_query(params), headers, resp 

831 ) 

832 

833 redirects += 1 

834 history.append(resp) 

835 if max_redirects and redirects >= max_redirects: 

836 if req._body is not None: 

837 await req._body.close() 

838 resp.close() 

839 raise TooManyRedirects( 

840 history[0].request_info, tuple(history) 

841 ) 

842 

843 # For 301 and 302, mimic IE, now changed in RFC 

844 # https://github.com/kennethreitz/requests/pull/269 

845 if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or ( 

846 resp.status in (301, 302) and resp.method == hdrs.METH_POST 

847 ): 

848 method = hdrs.METH_GET 

849 data = None 

850 if headers.get(hdrs.CONTENT_LENGTH): 

851 headers.pop(hdrs.CONTENT_LENGTH) 

852 else: 

853 # For 307/308, always preserve the request body 

854 # For 301/302 with non-POST methods, preserve the request body 

855 # https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1 

856 # Use the existing payload to avoid recreating it from 

857 # a potentially consumed file. 

858 # 

859 # If the payload is already consumed and cannot be replayed, 

860 # fail fast instead of silently sending an empty body. 

861 if req._body.consumed: 

862 resp.close() 

863 raise ClientPayloadError( 

864 "Cannot follow redirect with a consumed request " 

865 "body. Use bytes, a seekable file-like object, " 

866 "or set allow_redirects=False." 

867 ) 

868 data = req._body 

869 

870 r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get( 

871 hdrs.URI 

872 ) 

873 if r_url is None: 

874 # see github.com/aio-libs/aiohttp/issues/2022 

875 break 

876 else: 

877 # reading from correct redirection 

878 # response is forbidden 

879 resp.release() 

880 

881 try: 

882 parsed_redirect_url = URL( 

883 r_url, encoded=not self._requote_redirect_url 

884 ) 

885 except ValueError as e: 

886 if req._body is not None: 

887 await req._body.close() 

888 resp.close() 

889 raise InvalidUrlRedirectClientError( 

890 r_url, 

891 "Server attempted redirecting to a location that does not look like a URL", 

892 ) from e 

893 

894 scheme = parsed_redirect_url.scheme 

895 if scheme not in HTTP_AND_EMPTY_SCHEMA_SET: 

896 if req._body is not None: 

897 await req._body.close() 

898 resp.close() 

899 raise NonHttpUrlRedirectClientError(r_url) 

900 elif not scheme or ( 

901 scheme == url.scheme and _has_no_authority(r_url, scheme) 

902 ): 

903 # "http:/path" or "http:path" is a reference to 

904 # the current URL, as browsers resolve it. 

905 parsed_redirect_url = url.join(parsed_redirect_url) 

906 

907 try: 

908 redirect_origin = parsed_redirect_url.origin() 

909 except ValueError as origin_val_err: 

910 if req._body is not None: 

911 await req._body.close() 

912 resp.close() 

913 raise InvalidUrlRedirectClientError( 

914 parsed_redirect_url, 

915 "Invalid redirect URL origin", 

916 ) from origin_val_err 

917 

918 if url.origin() != redirect_origin: 

919 cookies = None 

920 headers.popall(hdrs.AUTHORIZATION, None) 

921 headers.popall(hdrs.COOKIE, None) 

922 headers.popall(hdrs.PROXY_AUTHORIZATION, None) 

923 

924 url = parsed_redirect_url 

925 params = {} 

926 resp.release() 

927 continue 

928 

929 break 

930 

931 if req._body is not None: 

932 await req._body.close() 

933 # check response status 

934 if raise_for_status is None: 

935 raise_for_status = self._raise_for_status 

936 

937 if callable(raise_for_status): 

938 await raise_for_status(resp) 

939 elif raise_for_status: 

940 resp.raise_for_status() 

941 

942 # register connection 

943 if handle is not None: 

944 if resp.connection is not None: 

945 resp.connection.add_callback(handle.cancel) 

946 else: 

947 handle.cancel() 

948 

949 resp._history = tuple(history) 

950 

951 for trace in traces: 

952 await trace.send_request_end( 

953 method, url.update_query(params), headers, resp 

954 ) 

955 if upload_tracker is not None: 

956 upload_tracker._finalize() 

957 return resp 

958 

959 except BaseException as e: 

960 # cleanup timer 

961 if tm is not None: 

962 tm.close() 

963 if handle: 

964 handle.cancel() 

965 

966 if upload_tracker is not None: 

967 upload_tracker._finalize() 

968 

969 if resp is not None: 

970 # A failure occurred after the response was received. 

971 resp.close() 

972 

973 if req is not None and req._body is not None: 

974 await req._body.close() 

975 

976 for trace in traces: 

977 # url and headers are bound whenever traces is non-empty. 

978 await trace.send_request_exception( 

979 method, url.update_query(params), headers, e # type: ignore[possibly-undefined, arg-type] 

980 ) 

981 raise 

982 

983 if TYPE_CHECKING: 

984 

985 @overload 

986 def ws_connect( 

987 self, 

988 url: StrOrURL, 

989 *, 

990 decode_text: Literal[True] = ..., 

991 **kwargs: Unpack[_WSConnectOptions], 

992 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[Literal[True]]]": ... 

993 

994 @overload 

995 def ws_connect( 

996 self, 

997 url: StrOrURL, 

998 *, 

999 decode_text: Literal[False], 

1000 **kwargs: Unpack[_WSConnectOptions], 

1001 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[Literal[False]]]": ... 

1002 

1003 @overload 

1004 def ws_connect( 

1005 self, 

1006 url: StrOrURL, 

1007 *, 

1008 decode_text: bool = ..., 

1009 **kwargs: Unpack[_WSConnectOptions], 

1010 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[bool]]": ... 

1011 

1012 def ws_connect( 

1013 self, 

1014 url: StrOrURL, 

1015 *, 

1016 method: str = hdrs.METH_GET, 

1017 protocols: Collection[str] = (), 

1018 timeout: ClientWSTimeout | _SENTINEL = sentinel, 

1019 receive_timeout: float | None = None, 

1020 autoclose: bool = True, 

1021 autoping: bool = True, 

1022 heartbeat: float | None = None, 

1023 origin: str | None = None, 

1024 params: Query = None, 

1025 headers: LooseHeaders | None = None, 

1026 proxy: StrOrURL | None = None, 

1027 ssl: SSLContext | bool | Fingerprint | _SENTINEL = sentinel, 

1028 server_hostname: str | None = None, 

1029 proxy_headers: LooseHeaders | None = None, 

1030 compress: int = 0, 

1031 max_msg_size: int = 4 * 1024 * 1024, 

1032 decode_text: bool = True, 

1033 ) -> "_BaseRequestContextManager[ClientWebSocketResponse[bool]]": 

1034 """Initiate websocket connection.""" 

1035 return _WSRequestContextManager( 

1036 self._ws_connect( 

1037 url, 

1038 method=method, 

1039 protocols=protocols, 

1040 timeout=timeout, 

1041 receive_timeout=receive_timeout, 

1042 autoclose=autoclose, 

1043 autoping=autoping, 

1044 heartbeat=heartbeat, 

1045 origin=origin, 

1046 params=params, 

1047 headers=headers, 

1048 proxy=proxy, 

1049 ssl=ssl, 

1050 server_hostname=server_hostname, 

1051 proxy_headers=proxy_headers, 

1052 compress=compress, 

1053 max_msg_size=max_msg_size, 

1054 decode_text=decode_text, 

1055 ) 

1056 ) 

1057 

1058 if TYPE_CHECKING: 

1059 

1060 @overload 

1061 async def _ws_connect( 

1062 self, 

1063 url: StrOrURL, 

1064 *, 

1065 decode_text: Literal[True] = ..., 

1066 **kwargs: Unpack[_WSConnectOptions], 

1067 ) -> "ClientWebSocketResponse[Literal[True]]": ... 

1068 

1069 @overload 

1070 async def _ws_connect( 

1071 self, 

1072 url: StrOrURL, 

1073 *, 

1074 decode_text: Literal[False], 

1075 **kwargs: Unpack[_WSConnectOptions], 

1076 ) -> "ClientWebSocketResponse[Literal[False]]": ... 

1077 

1078 @overload 

1079 async def _ws_connect( 

1080 self, 

1081 url: StrOrURL, 

1082 *, 

1083 decode_text: bool = ..., 

1084 **kwargs: Unpack[_WSConnectOptions], 

1085 ) -> "ClientWebSocketResponse[bool]": ... 

1086 

1087 async def _ws_connect( 

1088 self, 

1089 url: StrOrURL, 

1090 *, 

1091 method: str = hdrs.METH_GET, 

1092 protocols: Collection[str] = (), 

1093 timeout: ClientWSTimeout | _SENTINEL = sentinel, 

1094 receive_timeout: float | None = None, 

1095 autoclose: bool = True, 

1096 autoping: bool = True, 

1097 heartbeat: float | None = None, 

1098 origin: str | None = None, 

1099 params: Query = None, 

1100 headers: LooseHeaders | None = None, 

1101 proxy: StrOrURL | None = None, 

1102 ssl: SSLContext | bool | Fingerprint | _SENTINEL = sentinel, 

1103 server_hostname: str | None = None, 

1104 proxy_headers: LooseHeaders | None = None, 

1105 compress: int = 0, 

1106 max_msg_size: int = 4 * 1024 * 1024, 

1107 decode_text: bool = True, 

1108 ) -> "ClientWebSocketResponse[bool]": 

1109 if timeout is not sentinel: 

1110 if isinstance(timeout, ClientWSTimeout): 

1111 ws_timeout = timeout 

1112 else: 

1113 warnings.warn( # type: ignore[unreachable] 

1114 "parameter 'timeout' of type 'float' " 

1115 "is deprecated, please use " 

1116 "'timeout=ClientWSTimeout(ws_close=...)'", 

1117 DeprecationWarning, 

1118 stacklevel=2, 

1119 ) 

1120 ws_timeout = ClientWSTimeout(ws_close=timeout) 

1121 else: 

1122 ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT 

1123 if receive_timeout is not None: 

1124 warnings.warn( 

1125 "float parameter 'receive_timeout' " 

1126 "is deprecated, please use parameter " 

1127 "'timeout=ClientWSTimeout(ws_receive=...)'", 

1128 DeprecationWarning, 

1129 stacklevel=2, 

1130 ) 

1131 ws_timeout = dataclasses.replace(ws_timeout, ws_receive=receive_timeout) 

1132 

1133 if headers is None: 

1134 real_headers: CIMultiDict[str] = CIMultiDict() 

1135 else: 

1136 real_headers = CIMultiDict(headers) 

1137 

1138 default_headers = { 

1139 hdrs.UPGRADE: "websocket", 

1140 hdrs.CONNECTION: "Upgrade", 

1141 hdrs.SEC_WEBSOCKET_VERSION: "13", 

1142 } 

1143 

1144 for key, value in default_headers.items(): 

1145 real_headers.setdefault(key, value) 

1146 

1147 sec_key = base64.b64encode(os.urandom(16)) 

1148 real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode() 

1149 

1150 if protocols: 

1151 real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols) 

1152 if origin is not None: 

1153 real_headers[hdrs.ORIGIN] = origin 

1154 if compress: 

1155 extstr = ws_ext_gen(compress=compress) 

1156 real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr 

1157 

1158 if ssl is not sentinel and not isinstance(ssl, SSL_ALLOWED_TYPES): 

1159 raise TypeError( 

1160 "ssl should be SSLContext, Fingerprint, or bool, " 

1161 f"got {ssl!r} instead." 

1162 ) 

1163 

1164 # send request 

1165 resp = await self.request( 

1166 method, 

1167 url, 

1168 params=params, 

1169 headers=real_headers, 

1170 read_until_eof=False, 

1171 proxy=proxy, 

1172 ssl=ssl, 

1173 server_hostname=server_hostname, 

1174 proxy_headers=proxy_headers, 

1175 ) 

1176 

1177 try: 

1178 # check handshake 

1179 if resp.status != 101: 

1180 raise WSServerHandshakeError( 

1181 resp.request_info, 

1182 resp.history, 

1183 message="Invalid response status", 

1184 status=resp.status, 

1185 headers=resp.headers, 

1186 ) 

1187 

1188 if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket": 

1189 raise WSServerHandshakeError( 

1190 resp.request_info, 

1191 resp.history, 

1192 message="Invalid upgrade header", 

1193 status=resp.status, 

1194 headers=resp.headers, 

1195 ) 

1196 

1197 if not resp._upgraded: 

1198 raise WSServerHandshakeError( 

1199 resp.request_info, 

1200 resp.history, 

1201 message="Invalid connection header", 

1202 status=resp.status, 

1203 headers=resp.headers, 

1204 ) 

1205 

1206 # key calculation 

1207 r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "") 

1208 match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode() 

1209 if r_key != match: 

1210 raise WSServerHandshakeError( 

1211 resp.request_info, 

1212 resp.history, 

1213 message="Invalid challenge response", 

1214 status=resp.status, 

1215 headers=resp.headers, 

1216 ) 

1217 

1218 # websocket protocol 

1219 protocol = None 

1220 if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers: 

1221 resp_protocols = [ 

1222 proto.strip() 

1223 for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") 

1224 ] 

1225 

1226 for proto in resp_protocols: 

1227 if proto in protocols: 

1228 protocol = proto 

1229 break 

1230 

1231 # websocket compress 

1232 notakeover = False 

1233 if compress: 

1234 compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS) 

1235 if compress_hdrs: 

1236 try: 

1237 compress, notakeover = ws_ext_parse(compress_hdrs) 

1238 except WSHandshakeError as exc: 

1239 raise WSServerHandshakeError( 

1240 resp.request_info, 

1241 resp.history, 

1242 message=exc.args[0], 

1243 status=resp.status, 

1244 headers=resp.headers, 

1245 ) from exc 

1246 else: 

1247 compress = 0 

1248 notakeover = False 

1249 

1250 conn = resp.connection 

1251 assert conn is not None 

1252 conn_proto = conn.protocol 

1253 assert conn_proto is not None 

1254 

1255 # For WS connection the read_timeout must be either ws_timeout.ws_receive or greater 

1256 # None == no timeout, i.e. infinite timeout, so None is the max timeout possible 

1257 if ws_timeout.ws_receive is None: 

1258 # Reset regardless 

1259 conn_proto.read_timeout = None 

1260 elif conn_proto.read_timeout is not None: 

1261 conn_proto.read_timeout = max( 

1262 ws_timeout.ws_receive, conn_proto.read_timeout 

1263 ) 

1264 

1265 transport = conn.transport 

1266 assert transport is not None 

1267 reader = WebSocketDataQueue(conn_proto, DEFAULT_CHUNK_SIZE, loop=self._loop) 

1268 writer = WebSocketWriter( 

1269 conn_proto, 

1270 transport, 

1271 use_mask=True, 

1272 compress=compress, 

1273 notakeover=notakeover, 

1274 ) 

1275 except BaseException: 

1276 resp.close() 

1277 raise 

1278 else: 

1279 ws_resp = self._ws_response_class( 

1280 reader, 

1281 writer, 

1282 protocol, 

1283 resp, 

1284 ws_timeout, 

1285 autoclose, 

1286 autoping, 

1287 self._loop, 

1288 heartbeat=heartbeat, 

1289 compress=compress, 

1290 client_notakeover=notakeover, 

1291 ) 

1292 ws_resp._parser = WebSocketReader( 

1293 reader, 

1294 max_msg_size, 

1295 compress=bool(compress), 

1296 decode_text=decode_text, 

1297 ) 

1298 cb = None if heartbeat is None else ws_resp._on_data_received 

1299 conn_proto.set_parser(ws_resp._parser, reader, data_received_cb=cb) 

1300 return ws_resp 

1301 

1302 def _prepare_headers(self, headers: LooseHeaders | None) -> "CIMultiDict[str]": 

1303 """Add default headers and transform it to CIMultiDict""" 

1304 # Convert headers to MultiDict 

1305 result = CIMultiDict(self._default_headers) 

1306 if headers: 

1307 if not isinstance(headers, (MultiDictProxy, MultiDict)): 

1308 headers = CIMultiDict(headers) 

1309 added_names: set[str] = set() 

1310 for key, value in headers.items(): 

1311 if key in added_names: 

1312 result.add(key, value) 

1313 else: 

1314 result[key] = value 

1315 added_names.add(key) 

1316 return result 

1317 

1318 def _get_netrc_auth(self, host: str) -> str | None: 

1319 """Return an ``Authorization`` header value for ``host`` from netrc. 

1320 

1321 Designed to be called in an executor to avoid blocking I/O on the 

1322 event loop. 

1323 """ 

1324 netrc_obj = netrc_from_env() 

1325 try: 

1326 return _auth_header_from_netrc(netrc_obj, host) 

1327 except LookupError: 

1328 return None 

1329 

1330 if TYPE_CHECKING: 

1331 

1332 def get( 

1333 self, 

1334 url: StrOrURL, 

1335 **kwargs: Unpack[_RequestOptions], 

1336 ) -> "_RequestContextManager": ... 

1337 

1338 def options( 

1339 self, 

1340 url: StrOrURL, 

1341 **kwargs: Unpack[_RequestOptions], 

1342 ) -> "_RequestContextManager": ... 

1343 

1344 def head( 

1345 self, 

1346 url: StrOrURL, 

1347 **kwargs: Unpack[_RequestOptions], 

1348 ) -> "_RequestContextManager": ... 

1349 

1350 def post( 

1351 self, 

1352 url: StrOrURL, 

1353 **kwargs: Unpack[_RequestOptions], 

1354 ) -> "_RequestContextManager": ... 

1355 

1356 def put( 

1357 self, 

1358 url: StrOrURL, 

1359 **kwargs: Unpack[_RequestOptions], 

1360 ) -> "_RequestContextManager": ... 

1361 

1362 def patch( 

1363 self, 

1364 url: StrOrURL, 

1365 **kwargs: Unpack[_RequestOptions], 

1366 ) -> "_RequestContextManager": ... 

1367 

1368 def delete( 

1369 self, 

1370 url: StrOrURL, 

1371 **kwargs: Unpack[_RequestOptions], 

1372 ) -> "_RequestContextManager": ... 

1373 

1374 else: 

1375 

1376 def get( 

1377 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any 

1378 ) -> "_RequestContextManager": 

1379 """Perform HTTP GET request.""" 

1380 return _RequestContextManager( 

1381 self._request( 

1382 hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs 

1383 ) 

1384 ) 

1385 

1386 def options( 

1387 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any 

1388 ) -> "_RequestContextManager": 

1389 """Perform HTTP OPTIONS request.""" 

1390 return _RequestContextManager( 

1391 self._request( 

1392 hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs 

1393 ) 

1394 ) 

1395 

1396 def head( 

1397 self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any 

1398 ) -> "_RequestContextManager": 

1399 """Perform HTTP HEAD request.""" 

1400 return _RequestContextManager( 

1401 self._request( 

1402 hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs 

1403 ) 

1404 ) 

1405 

1406 def post( 

1407 self, url: StrOrURL, *, data: Any = None, **kwargs: Any 

1408 ) -> "_RequestContextManager": 

1409 """Perform HTTP POST request.""" 

1410 return _RequestContextManager( 

1411 self._request(hdrs.METH_POST, url, data=data, **kwargs) 

1412 ) 

1413 

1414 def put( 

1415 self, url: StrOrURL, *, data: Any = None, **kwargs: Any 

1416 ) -> "_RequestContextManager": 

1417 """Perform HTTP PUT request.""" 

1418 return _RequestContextManager( 

1419 self._request(hdrs.METH_PUT, url, data=data, **kwargs) 

1420 ) 

1421 

1422 def patch( 

1423 self, url: StrOrURL, *, data: Any = None, **kwargs: Any 

1424 ) -> "_RequestContextManager": 

1425 """Perform HTTP PATCH request.""" 

1426 return _RequestContextManager( 

1427 self._request(hdrs.METH_PATCH, url, data=data, **kwargs) 

1428 ) 

1429 

1430 def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager": 

1431 """Perform HTTP DELETE request.""" 

1432 return _RequestContextManager( 

1433 self._request(hdrs.METH_DELETE, url, **kwargs) 

1434 ) 

1435 

1436 async def close(self) -> None: 

1437 """Close underlying connector. 

1438 

1439 Release all acquired resources. 

1440 """ 

1441 if not self.closed: 

1442 if self._connector is not None and self._connector_owner: 

1443 await self._connector.close() 

1444 self._connector = None 

1445 

1446 @property 

1447 def closed(self) -> bool: 

1448 """Is client session closed. 

1449 

1450 A readonly property. 

1451 """ 

1452 return self._connector is None or self._connector.closed 

1453 

1454 @property 

1455 def connector(self) -> BaseConnector | None: 

1456 """Connector instance used for the session.""" 

1457 return self._connector 

1458 

1459 @property 

1460 def cookie_jar(self) -> AbstractCookieJar: 

1461 """The session cookies.""" 

1462 return self._cookie_jar 

1463 

1464 @property 

1465 def version(self) -> tuple[int, int]: 

1466 """The session HTTP protocol version.""" 

1467 return self._version 

1468 

1469 @property 

1470 def requote_redirect_url(self) -> bool: 

1471 """Do URL requoting on redirection handling.""" 

1472 return self._requote_redirect_url 

1473 

1474 @property 

1475 def timeout(self) -> ClientTimeout: 

1476 """Timeout for the session.""" 

1477 return self._timeout 

1478 

1479 @property 

1480 def headers(self) -> "CIMultiDict[str]": 

1481 """The default headers of the client session.""" 

1482 return self._default_headers 

1483 

1484 @property 

1485 def skip_auto_headers(self) -> frozenset[istr]: 

1486 """Headers for which autogeneration should be skipped""" 

1487 return self._skip_auto_headers 

1488 

1489 @property 

1490 def json_serialize(self) -> JSONEncoder: 

1491 """Json serializer callable""" 

1492 return self._json_serialize 

1493 

1494 @property 

1495 def connector_owner(self) -> bool: 

1496 """Should connector be closed on session closing""" 

1497 return self._connector_owner 

1498 

1499 @property 

1500 def raise_for_status( 

1501 self, 

1502 ) -> bool | Callable[[ClientResponse], Awaitable[None]]: 

1503 """Should `ClientResponse.raise_for_status()` be called for each response.""" 

1504 return self._raise_for_status 

1505 

1506 @property 

1507 def auto_decompress(self) -> bool: 

1508 """Should the body response be automatically decompressed.""" 

1509 return self._auto_decompress 

1510 

1511 @property 

1512 def trust_env(self) -> bool: 

1513 """ 

1514 Should proxies information from environment or netrc be trusted. 

1515 

1516 Information is from HTTP_PROXY / HTTPS_PROXY environment variables 

1517 or ~/.netrc file if present. 

1518 """ 

1519 return self._trust_env 

1520 

1521 @property 

1522 def trace_configs(self) -> list[TraceConfig[Any]]: 

1523 """A list of TraceConfig instances used for client tracing""" 

1524 return self._trace_configs 

1525 

1526 def detach(self) -> None: 

1527 """Detach connector from session without closing the former. 

1528 

1529 Session is switched to closed state anyway. 

1530 """ 

1531 self._connector = None 

1532 

1533 async def __aenter__(self) -> "ClientSession": 

1534 return self 

1535 

1536 async def __aexit__( 

1537 self, 

1538 exc_type: type[BaseException] | None, 

1539 exc_val: BaseException | None, 

1540 exc_tb: TracebackType | None, 

1541 ) -> None: 

1542 await self.close() 

1543 

1544 

1545class _BaseRequestContextManager( 

1546 Coroutine[Any, Any, _RetType_co], Generic[_RetType_co] 

1547): 

1548 __slots__ = ("_coro", "_resp") 

1549 

1550 def __init__(self, coro: Coroutine[asyncio.Future[Any], None, _RetType_co]) -> None: 

1551 self._coro: Coroutine[asyncio.Future[Any], None, _RetType_co] = coro 

1552 

1553 def send(self, arg: None) -> asyncio.Future[Any]: 

1554 return self._coro.send(arg) 

1555 

1556 def throw(self, *args: Any, **kwargs: Any) -> asyncio.Future[Any]: 

1557 return self._coro.throw(*args, **kwargs) 

1558 

1559 def close(self) -> None: 

1560 return self._coro.close() 

1561 

1562 def __await__(self) -> Generator[Any, None, _RetType_co]: 

1563 ret = self._coro.__await__() 

1564 return ret 

1565 

1566 def __iter__(self) -> Generator[Any, None, _RetType_co]: 

1567 return self.__await__() 

1568 

1569 async def __aenter__(self) -> _RetType_co: 

1570 self._resp: _RetType_co = await self._coro 

1571 return await self._resp.__aenter__() # type: ignore[return-value] 

1572 

1573 async def __aexit__( 

1574 self, 

1575 exc_type: type[BaseException] | None, 

1576 exc: BaseException | None, 

1577 tb: TracebackType | None, 

1578 ) -> None: 

1579 await self._resp.__aexit__(exc_type, exc, tb) 

1580 

1581 

1582_RequestContextManager = _BaseRequestContextManager[ClientResponse] 

1583_WSRequestContextManager = _BaseRequestContextManager[ClientWebSocketResponse[bool]] 

1584 

1585 

1586class _SessionRequestContextManager: 

1587 __slots__ = ("_coro", "_resp", "_session") 

1588 

1589 def __init__( 

1590 self, 

1591 coro: Coroutine[asyncio.Future[Any], None, ClientResponse], 

1592 session: ClientSession, 

1593 ) -> None: 

1594 self._coro = coro 

1595 self._resp: ClientResponse | None = None 

1596 self._session = session 

1597 

1598 async def __aenter__(self) -> ClientResponse: 

1599 try: 

1600 self._resp = await self._coro 

1601 except BaseException: 

1602 await self._session.close() 

1603 raise 

1604 else: 

1605 return self._resp 

1606 

1607 async def __aexit__( 

1608 self, 

1609 exc_type: type[BaseException] | None, 

1610 exc: BaseException | None, 

1611 tb: TracebackType | None, 

1612 ) -> None: 

1613 assert self._resp is not None 

1614 self._resp.close() 

1615 await self._session.close() 

1616 

1617 

1618if TYPE_CHECKING: 

1619 

1620 def request( 

1621 method: str, 

1622 url: StrOrURL, 

1623 *, 

1624 version: HttpVersion = http.HttpVersion11, 

1625 connector: BaseConnector | None = None, 

1626 **kwargs: Unpack[_RequestOptions], 

1627 ) -> _SessionRequestContextManager: ... 

1628 

1629else: 

1630 

1631 def request( 

1632 method: str, 

1633 url: StrOrURL, 

1634 *, 

1635 version: HttpVersion = http.HttpVersion11, 

1636 connector: BaseConnector | None = None, 

1637 **kwargs: Any, 

1638 ) -> _SessionRequestContextManager: 

1639 """Constructs and sends a request. 

1640 

1641 Returns response object. 

1642 method - HTTP method 

1643 url - request url 

1644 params - (optional) Dictionary or bytes to be sent in the query 

1645 string of the new request 

1646 data - (optional) Dictionary, bytes, or file-like object to 

1647 send in the body of the request 

1648 json - (optional) Any json compatible python object 

1649 headers - (optional) Dictionary of HTTP Headers to send with 

1650 the request 

1651 cookies - (optional) Dict object to send with the request 

1652 allow_redirects - (optional) If set to False, do not follow 

1653 redirects 

1654 version - Request HTTP version. 

1655 compress - Set to True if request has to be compressed 

1656 with deflate encoding. 

1657 chunked - Set to chunk size for chunked transfer encoding. 

1658 expect100 - Expect 100-continue response from server. 

1659 connector - BaseConnector sub-class instance to support 

1660 connection pooling. 

1661 read_until_eof - Read response until eof if response 

1662 does not have Content-Length header. 

1663 loop - Optional event loop. 

1664 timeout - Optional ClientTimeout settings structure, 5min 

1665 total timeout by default. 

1666 Usage:: 

1667 >>> import aiohttp 

1668 >>> async with aiohttp.request('GET', 'http://python.org/') as resp: 

1669 ... print(resp) 

1670 ... data = await resp.read() 

1671 <ClientResponse(https://www.python.org/) [200 OK]> 

1672 """ 

1673 connector_owner = False 

1674 if connector is None: 

1675 connector_owner = True 

1676 connector = TCPConnector(force_close=True) 

1677 

1678 session = ClientSession( 

1679 cookies=kwargs.pop("cookies", None), 

1680 version=version, 

1681 timeout=kwargs.pop("timeout", sentinel), 

1682 connector=connector, 

1683 connector_owner=connector_owner, 

1684 ) 

1685 

1686 return _SessionRequestContextManager( 

1687 session._request(method, url, **kwargs), 

1688 session, 

1689 )