Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/httpx/_client.py: 25%

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

534 statements  

1from __future__ import annotations 

2 

3import datetime 

4import enum 

5import logging 

6import typing 

7import warnings 

8from contextlib import asynccontextmanager, contextmanager 

9from types import TracebackType 

10 

11from .__version__ import __version__ 

12from ._auth import Auth, BasicAuth, FunctionAuth 

13from ._config import ( 

14 DEFAULT_LIMITS, 

15 DEFAULT_MAX_REDIRECTS, 

16 DEFAULT_TIMEOUT_CONFIG, 

17 Limits, 

18 Proxy, 

19 Timeout, 

20) 

21from ._decoders import SUPPORTED_DECODERS 

22from ._exceptions import ( 

23 InvalidURL, 

24 RemoteProtocolError, 

25 TooManyRedirects, 

26 request_context, 

27) 

28from ._models import Cookies, Headers, Request, Response 

29from ._status_codes import codes 

30from ._transports.asgi import ASGITransport 

31from ._transports.base import AsyncBaseTransport, BaseTransport 

32from ._transports.default import AsyncHTTPTransport, HTTPTransport 

33from ._transports.wsgi import WSGITransport 

34from ._types import ( 

35 AsyncByteStream, 

36 AuthTypes, 

37 CertTypes, 

38 CookieTypes, 

39 HeaderTypes, 

40 ProxiesTypes, 

41 ProxyTypes, 

42 QueryParamTypes, 

43 RequestContent, 

44 RequestData, 

45 RequestExtensions, 

46 RequestFiles, 

47 SyncByteStream, 

48 TimeoutTypes, 

49 URLTypes, 

50 VerifyTypes, 

51) 

52from ._urls import URL, QueryParams 

53from ._utils import ( 

54 Timer, 

55 URLPattern, 

56 get_environment_proxies, 

57 is_https_redirect, 

58 same_origin, 

59) 

60 

61# The type annotation for @classmethod and context managers here follows PEP 484 

62# https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods 

63T = typing.TypeVar("T", bound="Client") 

64U = typing.TypeVar("U", bound="AsyncClient") 

65 

66 

67class UseClientDefault: 

68 """ 

69 For some parameters such as `auth=...` and `timeout=...` we need to be able 

70 to indicate the default "unset" state, in a way that is distinctly different 

71 to using `None`. 

72 

73 The default "unset" state indicates that whatever default is set on the 

74 client should be used. This is different to setting `None`, which 

75 explicitly disables the parameter, possibly overriding a client default. 

76 

77 For example we use `timeout=USE_CLIENT_DEFAULT` in the `request()` signature. 

78 Omitting the `timeout` parameter will send a request using whatever default 

79 timeout has been configured on the client. Including `timeout=None` will 

80 ensure no timeout is used. 

81 

82 Note that user code shouldn't need to use the `USE_CLIENT_DEFAULT` constant, 

83 but it is used internally when a parameter is not included. 

84 """ 

85 

86 

87USE_CLIENT_DEFAULT = UseClientDefault() 

88 

89 

90logger = logging.getLogger("httpx") 

91 

92USER_AGENT = f"python-httpx/{__version__}" 

93ACCEPT_ENCODING = ", ".join( 

94 [key for key in SUPPORTED_DECODERS.keys() if key != "identity"] 

95) 

96 

97 

98class ClientState(enum.Enum): 

99 # UNOPENED: 

100 # The client has been instantiated, but has not been used to send a request, 

101 # or been opened by entering the context of a `with` block. 

102 UNOPENED = 1 

103 # OPENED: 

104 # The client has either sent a request, or is within a `with` block. 

105 OPENED = 2 

106 # CLOSED: 

107 # The client has either exited the `with` block, or `close()` has 

108 # been called explicitly. 

109 CLOSED = 3 

110 

111 

112class BoundSyncStream(SyncByteStream): 

113 """ 

114 A byte stream that is bound to a given response instance, and that 

115 ensures the `response.elapsed` is set once the response is closed. 

116 """ 

117 

118 def __init__( 

119 self, stream: SyncByteStream, response: Response, timer: Timer 

120 ) -> None: 

121 self._stream = stream 

122 self._response = response 

123 self._timer = timer 

124 

125 def __iter__(self) -> typing.Iterator[bytes]: 

126 for chunk in self._stream: 

127 yield chunk 

128 

129 def close(self) -> None: 

130 seconds = self._timer.sync_elapsed() 

131 self._response.elapsed = datetime.timedelta(seconds=seconds) 

132 self._stream.close() 

133 

134 

135class BoundAsyncStream(AsyncByteStream): 

136 """ 

137 An async byte stream that is bound to a given response instance, and that 

138 ensures the `response.elapsed` is set once the response is closed. 

139 """ 

140 

141 def __init__( 

142 self, stream: AsyncByteStream, response: Response, timer: Timer 

143 ) -> None: 

144 self._stream = stream 

145 self._response = response 

146 self._timer = timer 

147 

148 async def __aiter__(self) -> typing.AsyncIterator[bytes]: 

149 async for chunk in self._stream: 

150 yield chunk 

151 

152 async def aclose(self) -> None: 

153 seconds = await self._timer.async_elapsed() 

154 self._response.elapsed = datetime.timedelta(seconds=seconds) 

155 await self._stream.aclose() 

156 

157 

158EventHook = typing.Callable[..., typing.Any] 

159 

160 

161class BaseClient: 

162 def __init__( 

163 self, 

164 *, 

165 auth: AuthTypes | None = None, 

166 params: QueryParamTypes | None = None, 

167 headers: HeaderTypes | None = None, 

168 cookies: CookieTypes | None = None, 

169 timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 

170 follow_redirects: bool = False, 

171 max_redirects: int = DEFAULT_MAX_REDIRECTS, 

172 event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None, 

173 base_url: URLTypes = "", 

174 trust_env: bool = True, 

175 default_encoding: str | typing.Callable[[bytes], str] = "utf-8", 

176 ) -> None: 

177 event_hooks = {} if event_hooks is None else event_hooks 

178 

179 self._base_url = self._enforce_trailing_slash(URL(base_url)) 

180 

181 self._auth = self._build_auth(auth) 

182 self._params = QueryParams(params) 

183 self.headers = Headers(headers) 

184 self._cookies = Cookies(cookies) 

185 self._timeout = Timeout(timeout) 

186 self.follow_redirects = follow_redirects 

187 self.max_redirects = max_redirects 

188 self._event_hooks = { 

189 "request": list(event_hooks.get("request", [])), 

190 "response": list(event_hooks.get("response", [])), 

191 } 

192 self._trust_env = trust_env 

193 self._default_encoding = default_encoding 

194 self._state = ClientState.UNOPENED 

195 

196 @property 

197 def is_closed(self) -> bool: 

198 """ 

199 Check if the client being closed 

200 """ 

201 return self._state == ClientState.CLOSED 

202 

203 @property 

204 def trust_env(self) -> bool: 

205 return self._trust_env 

206 

207 def _enforce_trailing_slash(self, url: URL) -> URL: 

208 if url.raw_path.endswith(b"/"): 

209 return url 

210 return url.copy_with(raw_path=url.raw_path + b"/") 

211 

212 def _get_proxy_map( 

213 self, proxies: ProxiesTypes | None, allow_env_proxies: bool 

214 ) -> dict[str, Proxy | None]: 

215 if proxies is None: 

216 if allow_env_proxies: 

217 return { 

218 key: None if url is None else Proxy(url=url) 

219 for key, url in get_environment_proxies().items() 

220 } 

221 return {} 

222 if isinstance(proxies, dict): 

223 new_proxies = {} 

224 for key, value in proxies.items(): 

225 proxy = Proxy(url=value) if isinstance(value, (str, URL)) else value 

226 new_proxies[str(key)] = proxy 

227 return new_proxies 

228 else: 

229 proxy = Proxy(url=proxies) if isinstance(proxies, (str, URL)) else proxies 

230 return {"all://": proxy} 

231 

232 @property 

233 def timeout(self) -> Timeout: 

234 return self._timeout 

235 

236 @timeout.setter 

237 def timeout(self, timeout: TimeoutTypes) -> None: 

238 self._timeout = Timeout(timeout) 

239 

240 @property 

241 def event_hooks(self) -> dict[str, list[EventHook]]: 

242 return self._event_hooks 

243 

244 @event_hooks.setter 

245 def event_hooks(self, event_hooks: dict[str, list[EventHook]]) -> None: 

246 self._event_hooks = { 

247 "request": list(event_hooks.get("request", [])), 

248 "response": list(event_hooks.get("response", [])), 

249 } 

250 

251 @property 

252 def auth(self) -> Auth | None: 

253 """ 

254 Authentication class used when none is passed at the request-level. 

255 

256 See also [Authentication][0]. 

257 

258 [0]: /quickstart/#authentication 

259 """ 

260 return self._auth 

261 

262 @auth.setter 

263 def auth(self, auth: AuthTypes) -> None: 

264 self._auth = self._build_auth(auth) 

265 

266 @property 

267 def base_url(self) -> URL: 

268 """ 

269 Base URL to use when sending requests with relative URLs. 

270 """ 

271 return self._base_url 

272 

273 @base_url.setter 

274 def base_url(self, url: URLTypes) -> None: 

275 self._base_url = self._enforce_trailing_slash(URL(url)) 

276 

277 @property 

278 def headers(self) -> Headers: 

279 """ 

280 HTTP headers to include when sending requests. 

281 """ 

282 return self._headers 

283 

284 @headers.setter 

285 def headers(self, headers: HeaderTypes) -> None: 

286 client_headers = Headers( 

287 { 

288 b"Accept": b"*/*", 

289 b"Accept-Encoding": ACCEPT_ENCODING.encode("ascii"), 

290 b"Connection": b"keep-alive", 

291 b"User-Agent": USER_AGENT.encode("ascii"), 

292 } 

293 ) 

294 client_headers.update(headers) 

295 self._headers = client_headers 

296 

297 @property 

298 def cookies(self) -> Cookies: 

299 """ 

300 Cookie values to include when sending requests. 

301 """ 

302 return self._cookies 

303 

304 @cookies.setter 

305 def cookies(self, cookies: CookieTypes) -> None: 

306 self._cookies = Cookies(cookies) 

307 

308 @property 

309 def params(self) -> QueryParams: 

310 """ 

311 Query parameters to include in the URL when sending requests. 

312 """ 

313 return self._params 

314 

315 @params.setter 

316 def params(self, params: QueryParamTypes) -> None: 

317 self._params = QueryParams(params) 

318 

319 def build_request( 

320 self, 

321 method: str, 

322 url: URLTypes, 

323 *, 

324 content: RequestContent | None = None, 

325 data: RequestData | None = None, 

326 files: RequestFiles | None = None, 

327 json: typing.Any | None = None, 

328 params: QueryParamTypes | None = None, 

329 headers: HeaderTypes | None = None, 

330 cookies: CookieTypes | None = None, 

331 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

332 extensions: RequestExtensions | None = None, 

333 ) -> Request: 

334 """ 

335 Build and return a request instance. 

336 

337 * The `params`, `headers` and `cookies` arguments 

338 are merged with any values set on the client. 

339 * The `url` argument is merged with any `base_url` set on the client. 

340 

341 See also: [Request instances][0] 

342 

343 [0]: /advanced/#request-instances 

344 """ 

345 url = self._merge_url(url) 

346 headers = self._merge_headers(headers) 

347 cookies = self._merge_cookies(cookies) 

348 params = self._merge_queryparams(params) 

349 extensions = {} if extensions is None else extensions 

350 if "timeout" not in extensions: 

351 timeout = ( 

352 self.timeout 

353 if isinstance(timeout, UseClientDefault) 

354 else Timeout(timeout) 

355 ) 

356 extensions = dict(**extensions, timeout=timeout.as_dict()) 

357 return Request( 

358 method, 

359 url, 

360 content=content, 

361 data=data, 

362 files=files, 

363 json=json, 

364 params=params, 

365 headers=headers, 

366 cookies=cookies, 

367 extensions=extensions, 

368 ) 

369 

370 def _merge_url(self, url: URLTypes) -> URL: 

371 """ 

372 Merge a URL argument together with any 'base_url' on the client, 

373 to create the URL used for the outgoing request. 

374 """ 

375 merge_url = URL(url) 

376 if merge_url.is_relative_url: 

377 # To merge URLs we always append to the base URL. To get this 

378 # behaviour correct we always ensure the base URL ends in a '/' 

379 # separator, and strip any leading '/' from the merge URL. 

380 # 

381 # So, eg... 

382 # 

383 # >>> client = Client(base_url="https://www.example.com/subpath") 

384 # >>> client.base_url 

385 # URL('https://www.example.com/subpath/') 

386 # >>> client.build_request("GET", "/path").url 

387 # URL('https://www.example.com/subpath/path') 

388 merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/") 

389 return self.base_url.copy_with(raw_path=merge_raw_path) 

390 return merge_url 

391 

392 def _merge_cookies(self, cookies: CookieTypes | None = None) -> CookieTypes | None: 

393 """ 

394 Merge a cookies argument together with any cookies on the client, 

395 to create the cookies used for the outgoing request. 

396 """ 

397 if cookies or self.cookies: 

398 merged_cookies = Cookies(self.cookies) 

399 merged_cookies.update(cookies) 

400 return merged_cookies 

401 return cookies 

402 

403 def _merge_headers(self, headers: HeaderTypes | None = None) -> HeaderTypes | None: 

404 """ 

405 Merge a headers argument together with any headers on the client, 

406 to create the headers used for the outgoing request. 

407 """ 

408 merged_headers = Headers(self.headers) 

409 merged_headers.update(headers) 

410 return merged_headers 

411 

412 def _merge_queryparams( 

413 self, params: QueryParamTypes | None = None 

414 ) -> QueryParamTypes | None: 

415 """ 

416 Merge a queryparams argument together with any queryparams on the client, 

417 to create the queryparams used for the outgoing request. 

418 """ 

419 if params or self.params: 

420 merged_queryparams = QueryParams(self.params) 

421 return merged_queryparams.merge(params) 

422 return params 

423 

424 def _build_auth(self, auth: AuthTypes | None) -> Auth | None: 

425 if auth is None: 

426 return None 

427 elif isinstance(auth, tuple): 

428 return BasicAuth(username=auth[0], password=auth[1]) 

429 elif isinstance(auth, Auth): 

430 return auth 

431 elif callable(auth): 

432 return FunctionAuth(func=auth) 

433 else: 

434 raise TypeError(f'Invalid "auth" argument: {auth!r}') 

435 

436 def _build_request_auth( 

437 self, 

438 request: Request, 

439 auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, 

440 ) -> Auth: 

441 auth = ( 

442 self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth) 

443 ) 

444 

445 if auth is not None: 

446 return auth 

447 

448 username, password = request.url.username, request.url.password 

449 if username or password: 

450 return BasicAuth(username=username, password=password) 

451 

452 return Auth() 

453 

454 def _build_redirect_request(self, request: Request, response: Response) -> Request: 

455 """ 

456 Given a request and a redirect response, return a new request that 

457 should be used to effect the redirect. 

458 """ 

459 method = self._redirect_method(request, response) 

460 url = self._redirect_url(request, response) 

461 headers = self._redirect_headers(request, url, method) 

462 stream = self._redirect_stream(request, method) 

463 cookies = Cookies(self.cookies) 

464 return Request( 

465 method=method, 

466 url=url, 

467 headers=headers, 

468 cookies=cookies, 

469 stream=stream, 

470 extensions=request.extensions, 

471 ) 

472 

473 def _redirect_method(self, request: Request, response: Response) -> str: 

474 """ 

475 When being redirected we may want to change the method of the request 

476 based on certain specs or browser behavior. 

477 """ 

478 method = request.method 

479 

480 # https://tools.ietf.org/html/rfc7231#section-6.4.4 

481 if response.status_code == codes.SEE_OTHER and method != "HEAD": 

482 method = "GET" 

483 

484 # Do what the browsers do, despite standards... 

485 # Turn 302s into GETs. 

486 if response.status_code == codes.FOUND and method != "HEAD": 

487 method = "GET" 

488 

489 # If a POST is responded to with a 301, turn it into a GET. 

490 # This bizarre behaviour is explained in 'requests' issue 1704. 

491 if response.status_code == codes.MOVED_PERMANENTLY and method == "POST": 

492 method = "GET" 

493 

494 return method 

495 

496 def _redirect_url(self, request: Request, response: Response) -> URL: 

497 """ 

498 Return the URL for the redirect to follow. 

499 """ 

500 location = response.headers["Location"] 

501 

502 try: 

503 url = URL(location) 

504 except InvalidURL as exc: 

505 raise RemoteProtocolError( 

506 f"Invalid URL in location header: {exc}.", request=request 

507 ) from None 

508 

509 # Handle malformed 'Location' headers that are "absolute" form, have no host. 

510 # See: https://github.com/encode/httpx/issues/771 

511 if url.scheme and not url.host: 

512 url = url.copy_with(host=request.url.host) 

513 

514 # Facilitate relative 'Location' headers, as allowed by RFC 7231. 

515 # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') 

516 if url.is_relative_url: 

517 url = request.url.join(url) 

518 

519 # Attach previous fragment if needed (RFC 7231 7.1.2) 

520 if request.url.fragment and not url.fragment: 

521 url = url.copy_with(fragment=request.url.fragment) 

522 

523 return url 

524 

525 def _redirect_headers(self, request: Request, url: URL, method: str) -> Headers: 

526 """ 

527 Return the headers that should be used for the redirect request. 

528 """ 

529 headers = Headers(request.headers) 

530 

531 if not same_origin(url, request.url): 

532 if not is_https_redirect(request.url, url): 

533 # Strip Authorization headers when responses are redirected 

534 # away from the origin. (Except for direct HTTP to HTTPS redirects.) 

535 headers.pop("Authorization", None) 

536 

537 # Update the Host header. 

538 headers["Host"] = url.netloc.decode("ascii") 

539 

540 if method != request.method and method == "GET": 

541 # If we've switch to a 'GET' request, then strip any headers which 

542 # are only relevant to the request body. 

543 headers.pop("Content-Length", None) 

544 headers.pop("Transfer-Encoding", None) 

545 

546 # We should use the client cookie store to determine any cookie header, 

547 # rather than whatever was on the original outgoing request. 

548 headers.pop("Cookie", None) 

549 

550 return headers 

551 

552 def _redirect_stream( 

553 self, request: Request, method: str 

554 ) -> SyncByteStream | AsyncByteStream | None: 

555 """ 

556 Return the body that should be used for the redirect request. 

557 """ 

558 if method != request.method and method == "GET": 

559 return None 

560 

561 return request.stream 

562 

563 

564class Client(BaseClient): 

565 """ 

566 An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc. 

567 

568 It can be shared between threads. 

569 

570 Usage: 

571 

572 ```python 

573 >>> client = httpx.Client() 

574 >>> response = client.get('https://example.org') 

575 ``` 

576 

577 **Parameters:** 

578 

579 * **auth** - *(optional)* An authentication class to use when sending 

580 requests. 

581 * **params** - *(optional)* Query parameters to include in request URLs, as 

582 a string, dictionary, or sequence of two-tuples. 

583 * **headers** - *(optional)* Dictionary of HTTP headers to include when 

584 sending requests. 

585 * **cookies** - *(optional)* Dictionary of Cookie items to include when 

586 sending requests. 

587 * **verify** - *(optional)* SSL certificates (a.k.a CA bundle) used to 

588 verify the identity of requested hosts. Either `True` (default CA bundle), 

589 a path to an SSL certificate file, an `ssl.SSLContext`, or `False` 

590 (which will disable verification). 

591 * **cert** - *(optional)* An SSL certificate used by the requested host 

592 to authenticate the client. Either a path to an SSL certificate file, or 

593 two-tuple of (certificate file, key file), or a three-tuple of (certificate 

594 file, key file, password). 

595 * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be 

596 enabled. Defaults to `False`. 

597 * **proxy** - *(optional)* A proxy URL where all the traffic should be routed. 

598 * **proxies** - *(optional)* A dictionary mapping proxy keys to proxy 

599 URLs. 

600 * **timeout** - *(optional)* The timeout configuration to use when sending 

601 requests. 

602 * **limits** - *(optional)* The limits configuration to use. 

603 * **max_redirects** - *(optional)* The maximum number of redirect responses 

604 that should be followed. 

605 * **base_url** - *(optional)* A URL to use as the base when building 

606 request URLs. 

607 * **transport** - *(optional)* A transport class to use for sending requests 

608 over the network. 

609 * **app** - *(optional)* An WSGI application to send requests to, 

610 rather than sending actual network requests. 

611 * **trust_env** - *(optional)* Enables or disables usage of environment 

612 variables for configuration. 

613 * **default_encoding** - *(optional)* The default encoding to use for decoding 

614 response text, if no charset information is included in a response Content-Type 

615 header. Set to a callable for automatic character set detection. Default: "utf-8". 

616 """ 

617 

618 def __init__( 

619 self, 

620 *, 

621 auth: AuthTypes | None = None, 

622 params: QueryParamTypes | None = None, 

623 headers: HeaderTypes | None = None, 

624 cookies: CookieTypes | None = None, 

625 verify: VerifyTypes = True, 

626 cert: CertTypes | None = None, 

627 http1: bool = True, 

628 http2: bool = False, 

629 proxy: ProxyTypes | None = None, 

630 proxies: ProxiesTypes | None = None, 

631 mounts: None | (typing.Mapping[str, BaseTransport | None]) = None, 

632 timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 

633 follow_redirects: bool = False, 

634 limits: Limits = DEFAULT_LIMITS, 

635 max_redirects: int = DEFAULT_MAX_REDIRECTS, 

636 event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None, 

637 base_url: URLTypes = "", 

638 transport: BaseTransport | None = None, 

639 app: typing.Callable[..., typing.Any] | None = None, 

640 trust_env: bool = True, 

641 default_encoding: str | typing.Callable[[bytes], str] = "utf-8", 

642 ) -> None: 

643 super().__init__( 

644 auth=auth, 

645 params=params, 

646 headers=headers, 

647 cookies=cookies, 

648 timeout=timeout, 

649 follow_redirects=follow_redirects, 

650 max_redirects=max_redirects, 

651 event_hooks=event_hooks, 

652 base_url=base_url, 

653 trust_env=trust_env, 

654 default_encoding=default_encoding, 

655 ) 

656 

657 if http2: 

658 try: 

659 import h2 # noqa 

660 except ImportError: # pragma: no cover 

661 raise ImportError( 

662 "Using http2=True, but the 'h2' package is not installed. " 

663 "Make sure to install httpx using `pip install httpx[http2]`." 

664 ) from None 

665 

666 if proxies: 

667 message = ( 

668 "The 'proxies' argument is now deprecated." 

669 " Use 'proxy' or 'mounts' instead." 

670 ) 

671 warnings.warn(message, DeprecationWarning) 

672 if proxy: 

673 raise RuntimeError("Use either `proxy` or 'proxies', not both.") 

674 

675 if app: 

676 message = ( 

677 "The 'app' shortcut is now deprecated." 

678 " Use the explicit style 'transport=WSGITransport(app=...)' instead." 

679 ) 

680 warnings.warn(message, DeprecationWarning) 

681 

682 allow_env_proxies = trust_env and app is None and transport is None 

683 proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies) 

684 

685 self._transport = self._init_transport( 

686 verify=verify, 

687 cert=cert, 

688 http1=http1, 

689 http2=http2, 

690 limits=limits, 

691 transport=transport, 

692 app=app, 

693 trust_env=trust_env, 

694 ) 

695 self._mounts: dict[URLPattern, BaseTransport | None] = { 

696 URLPattern(key): None 

697 if proxy is None 

698 else self._init_proxy_transport( 

699 proxy, 

700 verify=verify, 

701 cert=cert, 

702 http1=http1, 

703 http2=http2, 

704 limits=limits, 

705 trust_env=trust_env, 

706 ) 

707 for key, proxy in proxy_map.items() 

708 } 

709 if mounts is not None: 

710 self._mounts.update( 

711 {URLPattern(key): transport for key, transport in mounts.items()} 

712 ) 

713 

714 self._mounts = dict(sorted(self._mounts.items())) 

715 

716 def _init_transport( 

717 self, 

718 verify: VerifyTypes = True, 

719 cert: CertTypes | None = None, 

720 http1: bool = True, 

721 http2: bool = False, 

722 limits: Limits = DEFAULT_LIMITS, 

723 transport: BaseTransport | None = None, 

724 app: typing.Callable[..., typing.Any] | None = None, 

725 trust_env: bool = True, 

726 ) -> BaseTransport: 

727 if transport is not None: 

728 return transport 

729 

730 if app is not None: 

731 return WSGITransport(app=app) 

732 

733 return HTTPTransport( 

734 verify=verify, 

735 cert=cert, 

736 http1=http1, 

737 http2=http2, 

738 limits=limits, 

739 trust_env=trust_env, 

740 ) 

741 

742 def _init_proxy_transport( 

743 self, 

744 proxy: Proxy, 

745 verify: VerifyTypes = True, 

746 cert: CertTypes | None = None, 

747 http1: bool = True, 

748 http2: bool = False, 

749 limits: Limits = DEFAULT_LIMITS, 

750 trust_env: bool = True, 

751 ) -> BaseTransport: 

752 return HTTPTransport( 

753 verify=verify, 

754 cert=cert, 

755 http1=http1, 

756 http2=http2, 

757 limits=limits, 

758 trust_env=trust_env, 

759 proxy=proxy, 

760 ) 

761 

762 def _transport_for_url(self, url: URL) -> BaseTransport: 

763 """ 

764 Returns the transport instance that should be used for a given URL. 

765 This will either be the standard connection pool, or a proxy. 

766 """ 

767 for pattern, transport in self._mounts.items(): 

768 if pattern.matches(url): 

769 return self._transport if transport is None else transport 

770 

771 return self._transport 

772 

773 def request( 

774 self, 

775 method: str, 

776 url: URLTypes, 

777 *, 

778 content: RequestContent | None = None, 

779 data: RequestData | None = None, 

780 files: RequestFiles | None = None, 

781 json: typing.Any | None = None, 

782 params: QueryParamTypes | None = None, 

783 headers: HeaderTypes | None = None, 

784 cookies: CookieTypes | None = None, 

785 auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, 

786 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

787 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

788 extensions: RequestExtensions | None = None, 

789 ) -> Response: 

790 """ 

791 Build and send a request. 

792 

793 Equivalent to: 

794 

795 ```python 

796 request = client.build_request(...) 

797 response = client.send(request, ...) 

798 ``` 

799 

800 See `Client.build_request()`, `Client.send()` and 

801 [Merging of configuration][0] for how the various parameters 

802 are merged with client-level configuration. 

803 

804 [0]: /advanced/#merging-of-configuration 

805 """ 

806 if cookies is not None: 

807 message = ( 

808 "Setting per-request cookies=<...> is being deprecated, because " 

809 "the expected behaviour on cookie persistence is ambiguous. Set " 

810 "cookies directly on the client instance instead." 

811 ) 

812 warnings.warn(message, DeprecationWarning) 

813 

814 request = self.build_request( 

815 method=method, 

816 url=url, 

817 content=content, 

818 data=data, 

819 files=files, 

820 json=json, 

821 params=params, 

822 headers=headers, 

823 cookies=cookies, 

824 timeout=timeout, 

825 extensions=extensions, 

826 ) 

827 return self.send(request, auth=auth, follow_redirects=follow_redirects) 

828 

829 @contextmanager 

830 def stream( 

831 self, 

832 method: str, 

833 url: URLTypes, 

834 *, 

835 content: RequestContent | None = None, 

836 data: RequestData | None = None, 

837 files: RequestFiles | None = None, 

838 json: typing.Any | None = None, 

839 params: QueryParamTypes | None = None, 

840 headers: HeaderTypes | None = None, 

841 cookies: CookieTypes | None = None, 

842 auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, 

843 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

844 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

845 extensions: RequestExtensions | None = None, 

846 ) -> typing.Iterator[Response]: 

847 """ 

848 Alternative to `httpx.request()` that streams the response body 

849 instead of loading it into memory at once. 

850 

851 **Parameters**: See `httpx.request`. 

852 

853 See also: [Streaming Responses][0] 

854 

855 [0]: /quickstart#streaming-responses 

856 """ 

857 request = self.build_request( 

858 method=method, 

859 url=url, 

860 content=content, 

861 data=data, 

862 files=files, 

863 json=json, 

864 params=params, 

865 headers=headers, 

866 cookies=cookies, 

867 timeout=timeout, 

868 extensions=extensions, 

869 ) 

870 response = self.send( 

871 request=request, 

872 auth=auth, 

873 follow_redirects=follow_redirects, 

874 stream=True, 

875 ) 

876 try: 

877 yield response 

878 finally: 

879 response.close() 

880 

881 def send( 

882 self, 

883 request: Request, 

884 *, 

885 stream: bool = False, 

886 auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, 

887 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

888 ) -> Response: 

889 """ 

890 Send a request. 

891 

892 The request is sent as-is, unmodified. 

893 

894 Typically you'll want to build one with `Client.build_request()` 

895 so that any client-level configuration is merged into the request, 

896 but passing an explicit `httpx.Request()` is supported as well. 

897 

898 See also: [Request instances][0] 

899 

900 [0]: /advanced/#request-instances 

901 """ 

902 if self._state == ClientState.CLOSED: 

903 raise RuntimeError("Cannot send a request, as the client has been closed.") 

904 

905 self._state = ClientState.OPENED 

906 follow_redirects = ( 

907 self.follow_redirects 

908 if isinstance(follow_redirects, UseClientDefault) 

909 else follow_redirects 

910 ) 

911 

912 auth = self._build_request_auth(request, auth) 

913 

914 response = self._send_handling_auth( 

915 request, 

916 auth=auth, 

917 follow_redirects=follow_redirects, 

918 history=[], 

919 ) 

920 try: 

921 if not stream: 

922 response.read() 

923 

924 return response 

925 

926 except BaseException as exc: 

927 response.close() 

928 raise exc 

929 

930 def _send_handling_auth( 

931 self, 

932 request: Request, 

933 auth: Auth, 

934 follow_redirects: bool, 

935 history: list[Response], 

936 ) -> Response: 

937 auth_flow = auth.sync_auth_flow(request) 

938 try: 

939 request = next(auth_flow) 

940 

941 while True: 

942 response = self._send_handling_redirects( 

943 request, 

944 follow_redirects=follow_redirects, 

945 history=history, 

946 ) 

947 try: 

948 try: 

949 next_request = auth_flow.send(response) 

950 except StopIteration: 

951 return response 

952 

953 response.history = list(history) 

954 response.read() 

955 request = next_request 

956 history.append(response) 

957 

958 except BaseException as exc: 

959 response.close() 

960 raise exc 

961 finally: 

962 auth_flow.close() 

963 

964 def _send_handling_redirects( 

965 self, 

966 request: Request, 

967 follow_redirects: bool, 

968 history: list[Response], 

969 ) -> Response: 

970 while True: 

971 if len(history) > self.max_redirects: 

972 raise TooManyRedirects( 

973 "Exceeded maximum allowed redirects.", request=request 

974 ) 

975 

976 for hook in self._event_hooks["request"]: 

977 hook(request) 

978 

979 response = self._send_single_request(request) 

980 try: 

981 for hook in self._event_hooks["response"]: 

982 hook(response) 

983 response.history = list(history) 

984 

985 if not response.has_redirect_location: 

986 return response 

987 

988 request = self._build_redirect_request(request, response) 

989 history = history + [response] 

990 

991 if follow_redirects: 

992 response.read() 

993 else: 

994 response.next_request = request 

995 return response 

996 

997 except BaseException as exc: 

998 response.close() 

999 raise exc 

1000 

1001 def _send_single_request(self, request: Request) -> Response: 

1002 """ 

1003 Sends a single request, without handling any redirections. 

1004 """ 

1005 transport = self._transport_for_url(request.url) 

1006 timer = Timer() 

1007 timer.sync_start() 

1008 

1009 if not isinstance(request.stream, SyncByteStream): 

1010 raise RuntimeError( 

1011 "Attempted to send an async request with a sync Client instance." 

1012 ) 

1013 

1014 with request_context(request=request): 

1015 response = transport.handle_request(request) 

1016 

1017 assert isinstance(response.stream, SyncByteStream) 

1018 

1019 response.request = request 

1020 response.stream = BoundSyncStream( 

1021 response.stream, response=response, timer=timer 

1022 ) 

1023 self.cookies.extract_cookies(response) 

1024 response.default_encoding = self._default_encoding 

1025 

1026 logger.info( 

1027 'HTTP Request: %s %s "%s %d %s"', 

1028 request.method, 

1029 request.url, 

1030 response.http_version, 

1031 response.status_code, 

1032 response.reason_phrase, 

1033 ) 

1034 

1035 return response 

1036 

1037 def get( 

1038 self, 

1039 url: URLTypes, 

1040 *, 

1041 params: QueryParamTypes | None = None, 

1042 headers: HeaderTypes | None = None, 

1043 cookies: CookieTypes | None = None, 

1044 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1045 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1046 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1047 extensions: RequestExtensions | None = None, 

1048 ) -> Response: 

1049 """ 

1050 Send a `GET` request. 

1051 

1052 **Parameters**: See `httpx.request`. 

1053 """ 

1054 return self.request( 

1055 "GET", 

1056 url, 

1057 params=params, 

1058 headers=headers, 

1059 cookies=cookies, 

1060 auth=auth, 

1061 follow_redirects=follow_redirects, 

1062 timeout=timeout, 

1063 extensions=extensions, 

1064 ) 

1065 

1066 def options( 

1067 self, 

1068 url: URLTypes, 

1069 *, 

1070 params: QueryParamTypes | None = None, 

1071 headers: HeaderTypes | None = None, 

1072 cookies: CookieTypes | None = None, 

1073 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1074 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1075 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1076 extensions: RequestExtensions | None = None, 

1077 ) -> Response: 

1078 """ 

1079 Send an `OPTIONS` request. 

1080 

1081 **Parameters**: See `httpx.request`. 

1082 """ 

1083 return self.request( 

1084 "OPTIONS", 

1085 url, 

1086 params=params, 

1087 headers=headers, 

1088 cookies=cookies, 

1089 auth=auth, 

1090 follow_redirects=follow_redirects, 

1091 timeout=timeout, 

1092 extensions=extensions, 

1093 ) 

1094 

1095 def head( 

1096 self, 

1097 url: URLTypes, 

1098 *, 

1099 params: QueryParamTypes | None = None, 

1100 headers: HeaderTypes | None = None, 

1101 cookies: CookieTypes | None = None, 

1102 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1103 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1104 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1105 extensions: RequestExtensions | None = None, 

1106 ) -> Response: 

1107 """ 

1108 Send a `HEAD` request. 

1109 

1110 **Parameters**: See `httpx.request`. 

1111 """ 

1112 return self.request( 

1113 "HEAD", 

1114 url, 

1115 params=params, 

1116 headers=headers, 

1117 cookies=cookies, 

1118 auth=auth, 

1119 follow_redirects=follow_redirects, 

1120 timeout=timeout, 

1121 extensions=extensions, 

1122 ) 

1123 

1124 def post( 

1125 self, 

1126 url: URLTypes, 

1127 *, 

1128 content: RequestContent | None = None, 

1129 data: RequestData | None = None, 

1130 files: RequestFiles | None = None, 

1131 json: typing.Any | None = None, 

1132 params: QueryParamTypes | None = None, 

1133 headers: HeaderTypes | None = None, 

1134 cookies: CookieTypes | None = None, 

1135 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1136 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1137 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1138 extensions: RequestExtensions | None = None, 

1139 ) -> Response: 

1140 """ 

1141 Send a `POST` request. 

1142 

1143 **Parameters**: See `httpx.request`. 

1144 """ 

1145 return self.request( 

1146 "POST", 

1147 url, 

1148 content=content, 

1149 data=data, 

1150 files=files, 

1151 json=json, 

1152 params=params, 

1153 headers=headers, 

1154 cookies=cookies, 

1155 auth=auth, 

1156 follow_redirects=follow_redirects, 

1157 timeout=timeout, 

1158 extensions=extensions, 

1159 ) 

1160 

1161 def put( 

1162 self, 

1163 url: URLTypes, 

1164 *, 

1165 content: RequestContent | None = None, 

1166 data: RequestData | None = None, 

1167 files: RequestFiles | None = None, 

1168 json: typing.Any | None = None, 

1169 params: QueryParamTypes | None = None, 

1170 headers: HeaderTypes | None = None, 

1171 cookies: CookieTypes | None = None, 

1172 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1173 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1174 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1175 extensions: RequestExtensions | None = None, 

1176 ) -> Response: 

1177 """ 

1178 Send a `PUT` request. 

1179 

1180 **Parameters**: See `httpx.request`. 

1181 """ 

1182 return self.request( 

1183 "PUT", 

1184 url, 

1185 content=content, 

1186 data=data, 

1187 files=files, 

1188 json=json, 

1189 params=params, 

1190 headers=headers, 

1191 cookies=cookies, 

1192 auth=auth, 

1193 follow_redirects=follow_redirects, 

1194 timeout=timeout, 

1195 extensions=extensions, 

1196 ) 

1197 

1198 def patch( 

1199 self, 

1200 url: URLTypes, 

1201 *, 

1202 content: RequestContent | None = None, 

1203 data: RequestData | None = None, 

1204 files: RequestFiles | None = None, 

1205 json: typing.Any | None = None, 

1206 params: QueryParamTypes | None = None, 

1207 headers: HeaderTypes | None = None, 

1208 cookies: CookieTypes | None = None, 

1209 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1210 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1211 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1212 extensions: RequestExtensions | None = None, 

1213 ) -> Response: 

1214 """ 

1215 Send a `PATCH` request. 

1216 

1217 **Parameters**: See `httpx.request`. 

1218 """ 

1219 return self.request( 

1220 "PATCH", 

1221 url, 

1222 content=content, 

1223 data=data, 

1224 files=files, 

1225 json=json, 

1226 params=params, 

1227 headers=headers, 

1228 cookies=cookies, 

1229 auth=auth, 

1230 follow_redirects=follow_redirects, 

1231 timeout=timeout, 

1232 extensions=extensions, 

1233 ) 

1234 

1235 def delete( 

1236 self, 

1237 url: URLTypes, 

1238 *, 

1239 params: QueryParamTypes | None = None, 

1240 headers: HeaderTypes | None = None, 

1241 cookies: CookieTypes | None = None, 

1242 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1243 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1244 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1245 extensions: RequestExtensions | None = None, 

1246 ) -> Response: 

1247 """ 

1248 Send a `DELETE` request. 

1249 

1250 **Parameters**: See `httpx.request`. 

1251 """ 

1252 return self.request( 

1253 "DELETE", 

1254 url, 

1255 params=params, 

1256 headers=headers, 

1257 cookies=cookies, 

1258 auth=auth, 

1259 follow_redirects=follow_redirects, 

1260 timeout=timeout, 

1261 extensions=extensions, 

1262 ) 

1263 

1264 def close(self) -> None: 

1265 """ 

1266 Close transport and proxies. 

1267 """ 

1268 if self._state != ClientState.CLOSED: 

1269 self._state = ClientState.CLOSED 

1270 

1271 self._transport.close() 

1272 for transport in self._mounts.values(): 

1273 if transport is not None: 

1274 transport.close() 

1275 

1276 def __enter__(self: T) -> T: 

1277 if self._state != ClientState.UNOPENED: 

1278 msg = { 

1279 ClientState.OPENED: "Cannot open a client instance more than once.", 

1280 ClientState.CLOSED: ( 

1281 "Cannot reopen a client instance, once it has been closed." 

1282 ), 

1283 }[self._state] 

1284 raise RuntimeError(msg) 

1285 

1286 self._state = ClientState.OPENED 

1287 

1288 self._transport.__enter__() 

1289 for transport in self._mounts.values(): 

1290 if transport is not None: 

1291 transport.__enter__() 

1292 return self 

1293 

1294 def __exit__( 

1295 self, 

1296 exc_type: type[BaseException] | None = None, 

1297 exc_value: BaseException | None = None, 

1298 traceback: TracebackType | None = None, 

1299 ) -> None: 

1300 self._state = ClientState.CLOSED 

1301 

1302 self._transport.__exit__(exc_type, exc_value, traceback) 

1303 for transport in self._mounts.values(): 

1304 if transport is not None: 

1305 transport.__exit__(exc_type, exc_value, traceback) 

1306 

1307 

1308class AsyncClient(BaseClient): 

1309 """ 

1310 An asynchronous HTTP client, with connection pooling, HTTP/2, redirects, 

1311 cookie persistence, etc. 

1312 

1313 It can be shared between tasks. 

1314 

1315 Usage: 

1316 

1317 ```python 

1318 >>> async with httpx.AsyncClient() as client: 

1319 >>> response = await client.get('https://example.org') 

1320 ``` 

1321 

1322 **Parameters:** 

1323 

1324 * **auth** - *(optional)* An authentication class to use when sending 

1325 requests. 

1326 * **params** - *(optional)* Query parameters to include in request URLs, as 

1327 a string, dictionary, or sequence of two-tuples. 

1328 * **headers** - *(optional)* Dictionary of HTTP headers to include when 

1329 sending requests. 

1330 * **cookies** - *(optional)* Dictionary of Cookie items to include when 

1331 sending requests. 

1332 * **verify** - *(optional)* SSL certificates (a.k.a CA bundle) used to 

1333 verify the identity of requested hosts. Either `True` (default CA bundle), 

1334 a path to an SSL certificate file, an `ssl.SSLContext`, or `False` 

1335 (which will disable verification). 

1336 * **cert** - *(optional)* An SSL certificate used by the requested host 

1337 to authenticate the client. Either a path to an SSL certificate file, or 

1338 two-tuple of (certificate file, key file), or a three-tuple of (certificate 

1339 file, key file, password). 

1340 * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be 

1341 enabled. Defaults to `False`. 

1342 * **proxy** - *(optional)* A proxy URL where all the traffic should be routed. 

1343 * **proxies** - *(optional)* A dictionary mapping HTTP protocols to proxy 

1344 URLs. 

1345 * **timeout** - *(optional)* The timeout configuration to use when sending 

1346 requests. 

1347 * **limits** - *(optional)* The limits configuration to use. 

1348 * **max_redirects** - *(optional)* The maximum number of redirect responses 

1349 that should be followed. 

1350 * **base_url** - *(optional)* A URL to use as the base when building 

1351 request URLs. 

1352 * **transport** - *(optional)* A transport class to use for sending requests 

1353 over the network. 

1354 * **app** - *(optional)* An ASGI application to send requests to, 

1355 rather than sending actual network requests. 

1356 * **trust_env** - *(optional)* Enables or disables usage of environment 

1357 variables for configuration. 

1358 * **default_encoding** - *(optional)* The default encoding to use for decoding 

1359 response text, if no charset information is included in a response Content-Type 

1360 header. Set to a callable for automatic character set detection. Default: "utf-8". 

1361 """ 

1362 

1363 def __init__( 

1364 self, 

1365 *, 

1366 auth: AuthTypes | None = None, 

1367 params: QueryParamTypes | None = None, 

1368 headers: HeaderTypes | None = None, 

1369 cookies: CookieTypes | None = None, 

1370 verify: VerifyTypes = True, 

1371 cert: CertTypes | None = None, 

1372 http1: bool = True, 

1373 http2: bool = False, 

1374 proxy: ProxyTypes | None = None, 

1375 proxies: ProxiesTypes | None = None, 

1376 mounts: None | (typing.Mapping[str, AsyncBaseTransport | None]) = None, 

1377 timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 

1378 follow_redirects: bool = False, 

1379 limits: Limits = DEFAULT_LIMITS, 

1380 max_redirects: int = DEFAULT_MAX_REDIRECTS, 

1381 event_hooks: None 

1382 | (typing.Mapping[str, list[typing.Callable[..., typing.Any]]]) = None, 

1383 base_url: URLTypes = "", 

1384 transport: AsyncBaseTransport | None = None, 

1385 app: typing.Callable[..., typing.Any] | None = None, 

1386 trust_env: bool = True, 

1387 default_encoding: str | typing.Callable[[bytes], str] = "utf-8", 

1388 ) -> None: 

1389 super().__init__( 

1390 auth=auth, 

1391 params=params, 

1392 headers=headers, 

1393 cookies=cookies, 

1394 timeout=timeout, 

1395 follow_redirects=follow_redirects, 

1396 max_redirects=max_redirects, 

1397 event_hooks=event_hooks, 

1398 base_url=base_url, 

1399 trust_env=trust_env, 

1400 default_encoding=default_encoding, 

1401 ) 

1402 

1403 if http2: 

1404 try: 

1405 import h2 # noqa 

1406 except ImportError: # pragma: no cover 

1407 raise ImportError( 

1408 "Using http2=True, but the 'h2' package is not installed. " 

1409 "Make sure to install httpx using `pip install httpx[http2]`." 

1410 ) from None 

1411 

1412 if proxies: 

1413 message = ( 

1414 "The 'proxies' argument is now deprecated." 

1415 " Use 'proxy' or 'mounts' instead." 

1416 ) 

1417 warnings.warn(message, DeprecationWarning) 

1418 if proxy: 

1419 raise RuntimeError("Use either `proxy` or 'proxies', not both.") 

1420 

1421 if app: 

1422 message = ( 

1423 "The 'app' shortcut is now deprecated." 

1424 " Use the explicit style 'transport=ASGITransport(app=...)' instead." 

1425 ) 

1426 warnings.warn(message, DeprecationWarning) 

1427 

1428 allow_env_proxies = trust_env and transport is None 

1429 proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies) 

1430 

1431 self._transport = self._init_transport( 

1432 verify=verify, 

1433 cert=cert, 

1434 http1=http1, 

1435 http2=http2, 

1436 limits=limits, 

1437 transport=transport, 

1438 app=app, 

1439 trust_env=trust_env, 

1440 ) 

1441 

1442 self._mounts: dict[URLPattern, AsyncBaseTransport | None] = { 

1443 URLPattern(key): None 

1444 if proxy is None 

1445 else self._init_proxy_transport( 

1446 proxy, 

1447 verify=verify, 

1448 cert=cert, 

1449 http1=http1, 

1450 http2=http2, 

1451 limits=limits, 

1452 trust_env=trust_env, 

1453 ) 

1454 for key, proxy in proxy_map.items() 

1455 } 

1456 if mounts is not None: 

1457 self._mounts.update( 

1458 {URLPattern(key): transport for key, transport in mounts.items()} 

1459 ) 

1460 self._mounts = dict(sorted(self._mounts.items())) 

1461 

1462 def _init_transport( 

1463 self, 

1464 verify: VerifyTypes = True, 

1465 cert: CertTypes | None = None, 

1466 http1: bool = True, 

1467 http2: bool = False, 

1468 limits: Limits = DEFAULT_LIMITS, 

1469 transport: AsyncBaseTransport | None = None, 

1470 app: typing.Callable[..., typing.Any] | None = None, 

1471 trust_env: bool = True, 

1472 ) -> AsyncBaseTransport: 

1473 if transport is not None: 

1474 return transport 

1475 

1476 if app is not None: 

1477 return ASGITransport(app=app) 

1478 

1479 return AsyncHTTPTransport( 

1480 verify=verify, 

1481 cert=cert, 

1482 http1=http1, 

1483 http2=http2, 

1484 limits=limits, 

1485 trust_env=trust_env, 

1486 ) 

1487 

1488 def _init_proxy_transport( 

1489 self, 

1490 proxy: Proxy, 

1491 verify: VerifyTypes = True, 

1492 cert: CertTypes | None = None, 

1493 http1: bool = True, 

1494 http2: bool = False, 

1495 limits: Limits = DEFAULT_LIMITS, 

1496 trust_env: bool = True, 

1497 ) -> AsyncBaseTransport: 

1498 return AsyncHTTPTransport( 

1499 verify=verify, 

1500 cert=cert, 

1501 http1=http1, 

1502 http2=http2, 

1503 limits=limits, 

1504 trust_env=trust_env, 

1505 proxy=proxy, 

1506 ) 

1507 

1508 def _transport_for_url(self, url: URL) -> AsyncBaseTransport: 

1509 """ 

1510 Returns the transport instance that should be used for a given URL. 

1511 This will either be the standard connection pool, or a proxy. 

1512 """ 

1513 for pattern, transport in self._mounts.items(): 

1514 if pattern.matches(url): 

1515 return self._transport if transport is None else transport 

1516 

1517 return self._transport 

1518 

1519 async def request( 

1520 self, 

1521 method: str, 

1522 url: URLTypes, 

1523 *, 

1524 content: RequestContent | None = None, 

1525 data: RequestData | None = None, 

1526 files: RequestFiles | None = None, 

1527 json: typing.Any | None = None, 

1528 params: QueryParamTypes | None = None, 

1529 headers: HeaderTypes | None = None, 

1530 cookies: CookieTypes | None = None, 

1531 auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, 

1532 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1533 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1534 extensions: RequestExtensions | None = None, 

1535 ) -> Response: 

1536 """ 

1537 Build and send a request. 

1538 

1539 Equivalent to: 

1540 

1541 ```python 

1542 request = client.build_request(...) 

1543 response = await client.send(request, ...) 

1544 ``` 

1545 

1546 See `AsyncClient.build_request()`, `AsyncClient.send()` 

1547 and [Merging of configuration][0] for how the various parameters 

1548 are merged with client-level configuration. 

1549 

1550 [0]: /advanced/#merging-of-configuration 

1551 """ 

1552 

1553 if cookies is not None: # pragma: no cover 

1554 message = ( 

1555 "Setting per-request cookies=<...> is being deprecated, because " 

1556 "the expected behaviour on cookie persistence is ambiguous. Set " 

1557 "cookies directly on the client instance instead." 

1558 ) 

1559 warnings.warn(message, DeprecationWarning) 

1560 

1561 request = self.build_request( 

1562 method=method, 

1563 url=url, 

1564 content=content, 

1565 data=data, 

1566 files=files, 

1567 json=json, 

1568 params=params, 

1569 headers=headers, 

1570 cookies=cookies, 

1571 timeout=timeout, 

1572 extensions=extensions, 

1573 ) 

1574 return await self.send(request, auth=auth, follow_redirects=follow_redirects) 

1575 

1576 @asynccontextmanager 

1577 async def stream( 

1578 self, 

1579 method: str, 

1580 url: URLTypes, 

1581 *, 

1582 content: RequestContent | None = None, 

1583 data: RequestData | None = None, 

1584 files: RequestFiles | None = None, 

1585 json: typing.Any | None = None, 

1586 params: QueryParamTypes | None = None, 

1587 headers: HeaderTypes | None = None, 

1588 cookies: CookieTypes | None = None, 

1589 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1590 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1591 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1592 extensions: RequestExtensions | None = None, 

1593 ) -> typing.AsyncIterator[Response]: 

1594 """ 

1595 Alternative to `httpx.request()` that streams the response body 

1596 instead of loading it into memory at once. 

1597 

1598 **Parameters**: See `httpx.request`. 

1599 

1600 See also: [Streaming Responses][0] 

1601 

1602 [0]: /quickstart#streaming-responses 

1603 """ 

1604 request = self.build_request( 

1605 method=method, 

1606 url=url, 

1607 content=content, 

1608 data=data, 

1609 files=files, 

1610 json=json, 

1611 params=params, 

1612 headers=headers, 

1613 cookies=cookies, 

1614 timeout=timeout, 

1615 extensions=extensions, 

1616 ) 

1617 response = await self.send( 

1618 request=request, 

1619 auth=auth, 

1620 follow_redirects=follow_redirects, 

1621 stream=True, 

1622 ) 

1623 try: 

1624 yield response 

1625 finally: 

1626 await response.aclose() 

1627 

1628 async def send( 

1629 self, 

1630 request: Request, 

1631 *, 

1632 stream: bool = False, 

1633 auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, 

1634 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1635 ) -> Response: 

1636 """ 

1637 Send a request. 

1638 

1639 The request is sent as-is, unmodified. 

1640 

1641 Typically you'll want to build one with `AsyncClient.build_request()` 

1642 so that any client-level configuration is merged into the request, 

1643 but passing an explicit `httpx.Request()` is supported as well. 

1644 

1645 See also: [Request instances][0] 

1646 

1647 [0]: /advanced/#request-instances 

1648 """ 

1649 if self._state == ClientState.CLOSED: 

1650 raise RuntimeError("Cannot send a request, as the client has been closed.") 

1651 

1652 self._state = ClientState.OPENED 

1653 follow_redirects = ( 

1654 self.follow_redirects 

1655 if isinstance(follow_redirects, UseClientDefault) 

1656 else follow_redirects 

1657 ) 

1658 

1659 auth = self._build_request_auth(request, auth) 

1660 

1661 response = await self._send_handling_auth( 

1662 request, 

1663 auth=auth, 

1664 follow_redirects=follow_redirects, 

1665 history=[], 

1666 ) 

1667 try: 

1668 if not stream: 

1669 await response.aread() 

1670 

1671 return response 

1672 

1673 except BaseException as exc: 

1674 await response.aclose() 

1675 raise exc 

1676 

1677 async def _send_handling_auth( 

1678 self, 

1679 request: Request, 

1680 auth: Auth, 

1681 follow_redirects: bool, 

1682 history: list[Response], 

1683 ) -> Response: 

1684 auth_flow = auth.async_auth_flow(request) 

1685 try: 

1686 request = await auth_flow.__anext__() 

1687 

1688 while True: 

1689 response = await self._send_handling_redirects( 

1690 request, 

1691 follow_redirects=follow_redirects, 

1692 history=history, 

1693 ) 

1694 try: 

1695 try: 

1696 next_request = await auth_flow.asend(response) 

1697 except StopAsyncIteration: 

1698 return response 

1699 

1700 response.history = list(history) 

1701 await response.aread() 

1702 request = next_request 

1703 history.append(response) 

1704 

1705 except BaseException as exc: 

1706 await response.aclose() 

1707 raise exc 

1708 finally: 

1709 await auth_flow.aclose() 

1710 

1711 async def _send_handling_redirects( 

1712 self, 

1713 request: Request, 

1714 follow_redirects: bool, 

1715 history: list[Response], 

1716 ) -> Response: 

1717 while True: 

1718 if len(history) > self.max_redirects: 

1719 raise TooManyRedirects( 

1720 "Exceeded maximum allowed redirects.", request=request 

1721 ) 

1722 

1723 for hook in self._event_hooks["request"]: 

1724 await hook(request) 

1725 

1726 response = await self._send_single_request(request) 

1727 try: 

1728 for hook in self._event_hooks["response"]: 

1729 await hook(response) 

1730 

1731 response.history = list(history) 

1732 

1733 if not response.has_redirect_location: 

1734 return response 

1735 

1736 request = self._build_redirect_request(request, response) 

1737 history = history + [response] 

1738 

1739 if follow_redirects: 

1740 await response.aread() 

1741 else: 

1742 response.next_request = request 

1743 return response 

1744 

1745 except BaseException as exc: 

1746 await response.aclose() 

1747 raise exc 

1748 

1749 async def _send_single_request(self, request: Request) -> Response: 

1750 """ 

1751 Sends a single request, without handling any redirections. 

1752 """ 

1753 transport = self._transport_for_url(request.url) 

1754 timer = Timer() 

1755 await timer.async_start() 

1756 

1757 if not isinstance(request.stream, AsyncByteStream): 

1758 raise RuntimeError( 

1759 "Attempted to send an sync request with an AsyncClient instance." 

1760 ) 

1761 

1762 with request_context(request=request): 

1763 response = await transport.handle_async_request(request) 

1764 

1765 assert isinstance(response.stream, AsyncByteStream) 

1766 response.request = request 

1767 response.stream = BoundAsyncStream( 

1768 response.stream, response=response, timer=timer 

1769 ) 

1770 self.cookies.extract_cookies(response) 

1771 response.default_encoding = self._default_encoding 

1772 

1773 logger.info( 

1774 'HTTP Request: %s %s "%s %d %s"', 

1775 request.method, 

1776 request.url, 

1777 response.http_version, 

1778 response.status_code, 

1779 response.reason_phrase, 

1780 ) 

1781 

1782 return response 

1783 

1784 async def get( 

1785 self, 

1786 url: URLTypes, 

1787 *, 

1788 params: QueryParamTypes | None = None, 

1789 headers: HeaderTypes | None = None, 

1790 cookies: CookieTypes | None = None, 

1791 auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, 

1792 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1793 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1794 extensions: RequestExtensions | None = None, 

1795 ) -> Response: 

1796 """ 

1797 Send a `GET` request. 

1798 

1799 **Parameters**: See `httpx.request`. 

1800 """ 

1801 return await self.request( 

1802 "GET", 

1803 url, 

1804 params=params, 

1805 headers=headers, 

1806 cookies=cookies, 

1807 auth=auth, 

1808 follow_redirects=follow_redirects, 

1809 timeout=timeout, 

1810 extensions=extensions, 

1811 ) 

1812 

1813 async def options( 

1814 self, 

1815 url: URLTypes, 

1816 *, 

1817 params: QueryParamTypes | None = None, 

1818 headers: HeaderTypes | None = None, 

1819 cookies: CookieTypes | None = None, 

1820 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1821 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1822 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1823 extensions: RequestExtensions | None = None, 

1824 ) -> Response: 

1825 """ 

1826 Send an `OPTIONS` request. 

1827 

1828 **Parameters**: See `httpx.request`. 

1829 """ 

1830 return await self.request( 

1831 "OPTIONS", 

1832 url, 

1833 params=params, 

1834 headers=headers, 

1835 cookies=cookies, 

1836 auth=auth, 

1837 follow_redirects=follow_redirects, 

1838 timeout=timeout, 

1839 extensions=extensions, 

1840 ) 

1841 

1842 async def head( 

1843 self, 

1844 url: URLTypes, 

1845 *, 

1846 params: QueryParamTypes | None = None, 

1847 headers: HeaderTypes | None = None, 

1848 cookies: CookieTypes | None = None, 

1849 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1850 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1851 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1852 extensions: RequestExtensions | None = None, 

1853 ) -> Response: 

1854 """ 

1855 Send a `HEAD` request. 

1856 

1857 **Parameters**: See `httpx.request`. 

1858 """ 

1859 return await self.request( 

1860 "HEAD", 

1861 url, 

1862 params=params, 

1863 headers=headers, 

1864 cookies=cookies, 

1865 auth=auth, 

1866 follow_redirects=follow_redirects, 

1867 timeout=timeout, 

1868 extensions=extensions, 

1869 ) 

1870 

1871 async def post( 

1872 self, 

1873 url: URLTypes, 

1874 *, 

1875 content: RequestContent | None = None, 

1876 data: RequestData | None = None, 

1877 files: RequestFiles | None = None, 

1878 json: typing.Any | None = None, 

1879 params: QueryParamTypes | None = None, 

1880 headers: HeaderTypes | None = None, 

1881 cookies: CookieTypes | None = None, 

1882 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1883 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1884 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1885 extensions: RequestExtensions | None = None, 

1886 ) -> Response: 

1887 """ 

1888 Send a `POST` request. 

1889 

1890 **Parameters**: See `httpx.request`. 

1891 """ 

1892 return await self.request( 

1893 "POST", 

1894 url, 

1895 content=content, 

1896 data=data, 

1897 files=files, 

1898 json=json, 

1899 params=params, 

1900 headers=headers, 

1901 cookies=cookies, 

1902 auth=auth, 

1903 follow_redirects=follow_redirects, 

1904 timeout=timeout, 

1905 extensions=extensions, 

1906 ) 

1907 

1908 async def put( 

1909 self, 

1910 url: URLTypes, 

1911 *, 

1912 content: RequestContent | None = None, 

1913 data: RequestData | None = None, 

1914 files: RequestFiles | None = None, 

1915 json: typing.Any | None = None, 

1916 params: QueryParamTypes | None = None, 

1917 headers: HeaderTypes | None = None, 

1918 cookies: CookieTypes | None = None, 

1919 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1920 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1921 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1922 extensions: RequestExtensions | None = None, 

1923 ) -> Response: 

1924 """ 

1925 Send a `PUT` request. 

1926 

1927 **Parameters**: See `httpx.request`. 

1928 """ 

1929 return await self.request( 

1930 "PUT", 

1931 url, 

1932 content=content, 

1933 data=data, 

1934 files=files, 

1935 json=json, 

1936 params=params, 

1937 headers=headers, 

1938 cookies=cookies, 

1939 auth=auth, 

1940 follow_redirects=follow_redirects, 

1941 timeout=timeout, 

1942 extensions=extensions, 

1943 ) 

1944 

1945 async def patch( 

1946 self, 

1947 url: URLTypes, 

1948 *, 

1949 content: RequestContent | None = None, 

1950 data: RequestData | None = None, 

1951 files: RequestFiles | None = None, 

1952 json: typing.Any | None = None, 

1953 params: QueryParamTypes | None = None, 

1954 headers: HeaderTypes | None = None, 

1955 cookies: CookieTypes | None = None, 

1956 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1957 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1958 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1959 extensions: RequestExtensions | None = None, 

1960 ) -> Response: 

1961 """ 

1962 Send a `PATCH` request. 

1963 

1964 **Parameters**: See `httpx.request`. 

1965 """ 

1966 return await self.request( 

1967 "PATCH", 

1968 url, 

1969 content=content, 

1970 data=data, 

1971 files=files, 

1972 json=json, 

1973 params=params, 

1974 headers=headers, 

1975 cookies=cookies, 

1976 auth=auth, 

1977 follow_redirects=follow_redirects, 

1978 timeout=timeout, 

1979 extensions=extensions, 

1980 ) 

1981 

1982 async def delete( 

1983 self, 

1984 url: URLTypes, 

1985 *, 

1986 params: QueryParamTypes | None = None, 

1987 headers: HeaderTypes | None = None, 

1988 cookies: CookieTypes | None = None, 

1989 auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1990 follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, 

1991 timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, 

1992 extensions: RequestExtensions | None = None, 

1993 ) -> Response: 

1994 """ 

1995 Send a `DELETE` request. 

1996 

1997 **Parameters**: See `httpx.request`. 

1998 """ 

1999 return await self.request( 

2000 "DELETE", 

2001 url, 

2002 params=params, 

2003 headers=headers, 

2004 cookies=cookies, 

2005 auth=auth, 

2006 follow_redirects=follow_redirects, 

2007 timeout=timeout, 

2008 extensions=extensions, 

2009 ) 

2010 

2011 async def aclose(self) -> None: 

2012 """ 

2013 Close transport and proxies. 

2014 """ 

2015 if self._state != ClientState.CLOSED: 

2016 self._state = ClientState.CLOSED 

2017 

2018 await self._transport.aclose() 

2019 for proxy in self._mounts.values(): 

2020 if proxy is not None: 

2021 await proxy.aclose() 

2022 

2023 async def __aenter__(self: U) -> U: 

2024 if self._state != ClientState.UNOPENED: 

2025 msg = { 

2026 ClientState.OPENED: "Cannot open a client instance more than once.", 

2027 ClientState.CLOSED: ( 

2028 "Cannot reopen a client instance, once it has been closed." 

2029 ), 

2030 }[self._state] 

2031 raise RuntimeError(msg) 

2032 

2033 self._state = ClientState.OPENED 

2034 

2035 await self._transport.__aenter__() 

2036 for proxy in self._mounts.values(): 

2037 if proxy is not None: 

2038 await proxy.__aenter__() 

2039 return self 

2040 

2041 async def __aexit__( 

2042 self, 

2043 exc_type: type[BaseException] | None = None, 

2044 exc_value: BaseException | None = None, 

2045 traceback: TracebackType | None = None, 

2046 ) -> None: 

2047 self._state = ClientState.CLOSED 

2048 

2049 await self._transport.__aexit__(exc_type, exc_value, traceback) 

2050 for proxy in self._mounts.values(): 

2051 if proxy is not None: 

2052 await proxy.__aexit__(exc_type, exc_value, traceback)