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

514 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 07:19 +0000

1import datetime 

2import enum 

3import logging 

4import typing 

5import warnings 

6from contextlib import asynccontextmanager, contextmanager 

7from types import TracebackType 

8 

9from .__version__ import __version__ 

10from ._auth import Auth, BasicAuth, FunctionAuth 

11from ._config import ( 

12 DEFAULT_LIMITS, 

13 DEFAULT_MAX_REDIRECTS, 

14 DEFAULT_TIMEOUT_CONFIG, 

15 Limits, 

16 Proxy, 

17 Timeout, 

18) 

19from ._decoders import SUPPORTED_DECODERS 

20from ._exceptions import ( 

21 InvalidURL, 

22 RemoteProtocolError, 

23 TooManyRedirects, 

24 request_context, 

25) 

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

27from ._status_codes import codes 

28from ._transports.asgi import ASGITransport 

29from ._transports.base import AsyncBaseTransport, BaseTransport 

30from ._transports.default import AsyncHTTPTransport, HTTPTransport 

31from ._transports.wsgi import WSGITransport 

32from ._types import ( 

33 AsyncByteStream, 

34 AuthTypes, 

35 CertTypes, 

36 CookieTypes, 

37 HeaderTypes, 

38 ProxiesTypes, 

39 QueryParamTypes, 

40 RequestContent, 

41 RequestData, 

42 RequestExtensions, 

43 RequestFiles, 

44 SyncByteStream, 

45 TimeoutTypes, 

46 URLTypes, 

47 VerifyTypes, 

48) 

49from ._urls import URL, QueryParams 

50from ._utils import ( 

51 Timer, 

52 URLPattern, 

53 get_environment_proxies, 

54 is_https_redirect, 

55 same_origin, 

56) 

57 

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

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

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

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

62 

63 

64class UseClientDefault: 

65 """ 

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

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

68 to using `None`. 

69 

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

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

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

73 

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

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

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

77 ensure no timeout is used. 

78 

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

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

81 """ 

82 

83 

84USE_CLIENT_DEFAULT = UseClientDefault() 

85 

86 

87logger = logging.getLogger("httpx") 

88 

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

90ACCEPT_ENCODING = ", ".join( 

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

92) 

93 

94 

95class ClientState(enum.Enum): 

96 # UNOPENED: 

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

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

99 UNOPENED = 1 

100 # OPENED: 

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

102 OPENED = 2 

103 # CLOSED: 

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

105 # been called explicitly. 

106 CLOSED = 3 

107 

108 

109class BoundSyncStream(SyncByteStream): 

110 """ 

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

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

113 """ 

114 

115 def __init__( 

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

117 ) -> None: 

118 self._stream = stream 

119 self._response = response 

120 self._timer = timer 

121 

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

123 for chunk in self._stream: 

124 yield chunk 

125 

126 def close(self) -> None: 

127 seconds = self._timer.sync_elapsed() 

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

129 self._stream.close() 

130 

131 

132class BoundAsyncStream(AsyncByteStream): 

133 """ 

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

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

136 """ 

137 

138 def __init__( 

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

140 ) -> None: 

141 self._stream = stream 

142 self._response = response 

143 self._timer = timer 

144 

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

146 async for chunk in self._stream: 

147 yield chunk 

148 

149 async def aclose(self) -> None: 

150 seconds = await self._timer.async_elapsed() 

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

152 await self._stream.aclose() 

153 

154 

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

156 

157 

158class BaseClient: 

159 def __init__( 

160 self, 

161 *, 

162 auth: typing.Optional[AuthTypes] = None, 

163 params: typing.Optional[QueryParamTypes] = None, 

164 headers: typing.Optional[HeaderTypes] = None, 

165 cookies: typing.Optional[CookieTypes] = None, 

166 timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 

167 follow_redirects: bool = False, 

168 max_redirects: int = DEFAULT_MAX_REDIRECTS, 

169 event_hooks: typing.Optional[ 

170 typing.Mapping[str, typing.List[EventHook]] 

171 ] = None, 

172 base_url: URLTypes = "", 

173 trust_env: bool = True, 

174 default_encoding: typing.Union[str, typing.Callable[[bytes], str]] = "utf-8", 

175 ): 

176 event_hooks = {} if event_hooks is None else event_hooks 

177 

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

179 

180 self._auth = self._build_auth(auth) 

181 self._params = QueryParams(params) 

182 self.headers = Headers(headers) 

183 self._cookies = Cookies(cookies) 

184 self._timeout = Timeout(timeout) 

185 self.follow_redirects = follow_redirects 

186 self.max_redirects = max_redirects 

187 self._event_hooks = { 

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

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

190 } 

191 self._trust_env = trust_env 

192 self._default_encoding = default_encoding 

193 self._state = ClientState.UNOPENED 

194 

195 @property 

196 def is_closed(self) -> bool: 

197 """ 

198 Check if the client being closed 

199 """ 

200 return self._state == ClientState.CLOSED 

201 

202 @property 

203 def trust_env(self) -> bool: 

204 return self._trust_env 

205 

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

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

208 return url 

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

210 

211 def _get_proxy_map( 

212 self, proxies: typing.Optional[ProxiesTypes], allow_env_proxies: bool 

213 ) -> typing.Dict[str, typing.Optional[Proxy]]: 

214 if proxies is None: 

215 if allow_env_proxies: 

216 return { 

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

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

219 } 

220 return {} 

221 if isinstance(proxies, dict): 

222 new_proxies = {} 

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

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

225 new_proxies[str(key)] = proxy 

226 return new_proxies 

227 else: 

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

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

230 

231 @property 

232 def timeout(self) -> Timeout: 

233 return self._timeout 

234 

235 @timeout.setter 

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

237 self._timeout = Timeout(timeout) 

238 

239 @property 

240 def event_hooks(self) -> typing.Dict[str, typing.List[EventHook]]: 

241 return self._event_hooks 

242 

243 @event_hooks.setter 

244 def event_hooks( 

245 self, event_hooks: typing.Dict[str, typing.List[EventHook]] 

246 ) -> None: 

247 self._event_hooks = { 

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

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

250 } 

251 

252 @property 

253 def auth(self) -> typing.Optional[Auth]: 

254 """ 

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

256 

257 See also [Authentication][0]. 

258 

259 [0]: /quickstart/#authentication 

260 """ 

261 return self._auth 

262 

263 @auth.setter 

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

265 self._auth = self._build_auth(auth) 

266 

267 @property 

268 def base_url(self) -> URL: 

269 """ 

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

271 """ 

272 return self._base_url 

273 

274 @base_url.setter 

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

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

277 

278 @property 

279 def headers(self) -> Headers: 

280 """ 

281 HTTP headers to include when sending requests. 

282 """ 

283 return self._headers 

284 

285 @headers.setter 

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

287 client_headers = Headers( 

288 { 

289 b"Accept": b"*/*", 

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

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

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

293 } 

294 ) 

295 client_headers.update(headers) 

296 self._headers = client_headers 

297 

298 @property 

299 def cookies(self) -> Cookies: 

300 """ 

301 Cookie values to include when sending requests. 

302 """ 

303 return self._cookies 

304 

305 @cookies.setter 

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

307 self._cookies = Cookies(cookies) 

308 

309 @property 

310 def params(self) -> QueryParams: 

311 """ 

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

313 """ 

314 return self._params 

315 

316 @params.setter 

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

318 self._params = QueryParams(params) 

319 

320 def build_request( 

321 self, 

322 method: str, 

323 url: URLTypes, 

324 *, 

325 content: typing.Optional[RequestContent] = None, 

326 data: typing.Optional[RequestData] = None, 

327 files: typing.Optional[RequestFiles] = None, 

328 json: typing.Optional[typing.Any] = None, 

329 params: typing.Optional[QueryParamTypes] = None, 

330 headers: typing.Optional[HeaderTypes] = None, 

331 cookies: typing.Optional[CookieTypes] = None, 

332 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

333 extensions: typing.Optional[RequestExtensions] = None, 

334 ) -> Request: 

335 """ 

336 Build and return a request instance. 

337 

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

339 are merged with any values set on the client. 

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

341 

342 See also: [Request instances][0] 

343 

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

345 """ 

346 url = self._merge_url(url) 

347 headers = self._merge_headers(headers) 

348 cookies = self._merge_cookies(cookies) 

349 params = self._merge_queryparams(params) 

350 extensions = {} if extensions is None else extensions 

351 if "timeout" not in extensions: 

352 timeout = ( 

353 self.timeout 

354 if isinstance(timeout, UseClientDefault) 

355 else Timeout(timeout) 

356 ) 

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

358 return Request( 

359 method, 

360 url, 

361 content=content, 

362 data=data, 

363 files=files, 

364 json=json, 

365 params=params, 

366 headers=headers, 

367 cookies=cookies, 

368 extensions=extensions, 

369 ) 

370 

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

372 """ 

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

374 to create the URL used for the outgoing request. 

375 """ 

376 merge_url = URL(url) 

377 if merge_url.is_relative_url: 

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

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

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

381 # 

382 # So, eg... 

383 # 

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

385 # >>> client.base_url 

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

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

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

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

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

391 return merge_url 

392 

393 def _merge_cookies( 

394 self, cookies: typing.Optional[CookieTypes] = None 

395 ) -> typing.Optional[CookieTypes]: 

396 """ 

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

398 to create the cookies used for the outgoing request. 

399 """ 

400 if cookies or self.cookies: 

401 merged_cookies = Cookies(self.cookies) 

402 merged_cookies.update(cookies) 

403 return merged_cookies 

404 return cookies 

405 

406 def _merge_headers( 

407 self, headers: typing.Optional[HeaderTypes] = None 

408 ) -> typing.Optional[HeaderTypes]: 

409 """ 

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

411 to create the headers used for the outgoing request. 

412 """ 

413 merged_headers = Headers(self.headers) 

414 merged_headers.update(headers) 

415 return merged_headers 

416 

417 def _merge_queryparams( 

418 self, params: typing.Optional[QueryParamTypes] = None 

419 ) -> typing.Optional[QueryParamTypes]: 

420 """ 

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

422 to create the queryparams used for the outgoing request. 

423 """ 

424 if params or self.params: 

425 merged_queryparams = QueryParams(self.params) 

426 return merged_queryparams.merge(params) 

427 return params 

428 

429 def _build_auth(self, auth: typing.Optional[AuthTypes]) -> typing.Optional[Auth]: 

430 if auth is None: 

431 return None 

432 elif isinstance(auth, tuple): 

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

434 elif isinstance(auth, Auth): 

435 return auth 

436 elif callable(auth): 

437 return FunctionAuth(func=auth) 

438 else: 

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

440 

441 def _build_request_auth( 

442 self, 

443 request: Request, 

444 auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT, 

445 ) -> Auth: 

446 auth = ( 

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

448 ) 

449 

450 if auth is not None: 

451 return auth 

452 

453 username, password = request.url.username, request.url.password 

454 if username or password: 

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

456 

457 return Auth() 

458 

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

460 """ 

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

462 should be used to effect the redirect. 

463 """ 

464 method = self._redirect_method(request, response) 

465 url = self._redirect_url(request, response) 

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

467 stream = self._redirect_stream(request, method) 

468 cookies = Cookies(self.cookies) 

469 return Request( 

470 method=method, 

471 url=url, 

472 headers=headers, 

473 cookies=cookies, 

474 stream=stream, 

475 extensions=request.extensions, 

476 ) 

477 

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

479 """ 

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

481 based on certain specs or browser behavior. 

482 """ 

483 method = request.method 

484 

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

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

487 method = "GET" 

488 

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

490 # Turn 302s into GETs. 

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

492 method = "GET" 

493 

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

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

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

497 method = "GET" 

498 

499 return method 

500 

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

502 """ 

503 Return the URL for the redirect to follow. 

504 """ 

505 location = response.headers["Location"] 

506 

507 try: 

508 url = URL(location) 

509 except InvalidURL as exc: 

510 raise RemoteProtocolError( 

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

512 ) from None 

513 

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

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

516 if url.scheme and not url.host: 

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

518 

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

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

521 if url.is_relative_url: 

522 url = request.url.join(url) 

523 

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

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

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

527 

528 return url 

529 

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

531 """ 

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

533 """ 

534 headers = Headers(request.headers) 

535 

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

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

538 # Strip Authorization headers when responses are redirected 

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

540 headers.pop("Authorization", None) 

541 

542 # Update the Host header. 

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

544 

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

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

547 # are only relevant to the request body. 

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

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

550 

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

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

553 headers.pop("Cookie", None) 

554 

555 return headers 

556 

557 def _redirect_stream( 

558 self, request: Request, method: str 

559 ) -> typing.Optional[typing.Union[SyncByteStream, AsyncByteStream]]: 

560 """ 

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

562 """ 

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

564 return None 

565 

566 return request.stream 

567 

568 

569class Client(BaseClient): 

570 """ 

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

572 

573 It can be shared between threads. 

574 

575 Usage: 

576 

577 ```python 

578 >>> client = httpx.Client() 

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

580 ``` 

581 

582 **Parameters:** 

583 

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

585 requests. 

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

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

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

589 sending requests. 

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

591 sending requests. 

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

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

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

595 (which will disable verification). 

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

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

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

599 file, key file, password). 

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

601 URLs. 

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

603 requests. 

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

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

606 that should be followed. 

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

608 request URLs. 

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

610 over the network. 

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

612 rather than sending actual network requests. 

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

614 variables for configuration. 

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

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

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

618 """ 

619 

620 def __init__( 

621 self, 

622 *, 

623 auth: typing.Optional[AuthTypes] = None, 

624 params: typing.Optional[QueryParamTypes] = None, 

625 headers: typing.Optional[HeaderTypes] = None, 

626 cookies: typing.Optional[CookieTypes] = None, 

627 verify: VerifyTypes = True, 

628 cert: typing.Optional[CertTypes] = None, 

629 http1: bool = True, 

630 http2: bool = False, 

631 proxies: typing.Optional[ProxiesTypes] = None, 

632 mounts: typing.Optional[typing.Mapping[str, BaseTransport]] = None, 

633 timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 

634 follow_redirects: bool = False, 

635 limits: Limits = DEFAULT_LIMITS, 

636 max_redirects: int = DEFAULT_MAX_REDIRECTS, 

637 event_hooks: typing.Optional[ 

638 typing.Mapping[str, typing.List[EventHook]] 

639 ] = None, 

640 base_url: URLTypes = "", 

641 transport: typing.Optional[BaseTransport] = None, 

642 app: typing.Optional[typing.Callable[..., typing.Any]] = None, 

643 trust_env: bool = True, 

644 default_encoding: typing.Union[str, typing.Callable[[bytes], str]] = "utf-8", 

645 ): 

646 super().__init__( 

647 auth=auth, 

648 params=params, 

649 headers=headers, 

650 cookies=cookies, 

651 timeout=timeout, 

652 follow_redirects=follow_redirects, 

653 max_redirects=max_redirects, 

654 event_hooks=event_hooks, 

655 base_url=base_url, 

656 trust_env=trust_env, 

657 default_encoding=default_encoding, 

658 ) 

659 

660 if http2: 

661 try: 

662 import h2 # noqa 

663 except ImportError: # pragma: no cover 

664 raise ImportError( 

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

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

667 ) from None 

668 

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

670 proxy_map = self._get_proxy_map(proxies, allow_env_proxies) 

671 

672 self._transport = self._init_transport( 

673 verify=verify, 

674 cert=cert, 

675 http1=http1, 

676 http2=http2, 

677 limits=limits, 

678 transport=transport, 

679 app=app, 

680 trust_env=trust_env, 

681 ) 

682 self._mounts: typing.Dict[URLPattern, typing.Optional[BaseTransport]] = { 

683 URLPattern(key): None 

684 if proxy is None 

685 else self._init_proxy_transport( 

686 proxy, 

687 verify=verify, 

688 cert=cert, 

689 http1=http1, 

690 http2=http2, 

691 limits=limits, 

692 trust_env=trust_env, 

693 ) 

694 for key, proxy in proxy_map.items() 

695 } 

696 if mounts is not None: 

697 self._mounts.update( 

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

699 ) 

700 

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

702 

703 def _init_transport( 

704 self, 

705 verify: VerifyTypes = True, 

706 cert: typing.Optional[CertTypes] = None, 

707 http1: bool = True, 

708 http2: bool = False, 

709 limits: Limits = DEFAULT_LIMITS, 

710 transport: typing.Optional[BaseTransport] = None, 

711 app: typing.Optional[typing.Callable[..., typing.Any]] = None, 

712 trust_env: bool = True, 

713 ) -> BaseTransport: 

714 if transport is not None: 

715 return transport 

716 

717 if app is not None: 

718 return WSGITransport(app=app) 

719 

720 return HTTPTransport( 

721 verify=verify, 

722 cert=cert, 

723 http1=http1, 

724 http2=http2, 

725 limits=limits, 

726 trust_env=trust_env, 

727 ) 

728 

729 def _init_proxy_transport( 

730 self, 

731 proxy: Proxy, 

732 verify: VerifyTypes = True, 

733 cert: typing.Optional[CertTypes] = None, 

734 http1: bool = True, 

735 http2: bool = False, 

736 limits: Limits = DEFAULT_LIMITS, 

737 trust_env: bool = True, 

738 ) -> BaseTransport: 

739 return HTTPTransport( 

740 verify=verify, 

741 cert=cert, 

742 http1=http1, 

743 http2=http2, 

744 limits=limits, 

745 trust_env=trust_env, 

746 proxy=proxy, 

747 ) 

748 

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

750 """ 

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

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

753 """ 

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

755 if pattern.matches(url): 

756 return self._transport if transport is None else transport 

757 

758 return self._transport 

759 

760 def request( 

761 self, 

762 method: str, 

763 url: URLTypes, 

764 *, 

765 content: typing.Optional[RequestContent] = None, 

766 data: typing.Optional[RequestData] = None, 

767 files: typing.Optional[RequestFiles] = None, 

768 json: typing.Optional[typing.Any] = None, 

769 params: typing.Optional[QueryParamTypes] = None, 

770 headers: typing.Optional[HeaderTypes] = None, 

771 cookies: typing.Optional[CookieTypes] = None, 

772 auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT, 

773 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

774 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

775 extensions: typing.Optional[RequestExtensions] = None, 

776 ) -> Response: 

777 """ 

778 Build and send a request. 

779 

780 Equivalent to: 

781 

782 ```python 

783 request = client.build_request(...) 

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

785 ``` 

786 

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

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

789 are merged with client-level configuration. 

790 

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

792 """ 

793 if cookies is not None: 

794 message = ( 

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

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

797 "cookies directly on the client instance instead." 

798 ) 

799 warnings.warn(message, DeprecationWarning) 

800 

801 request = self.build_request( 

802 method=method, 

803 url=url, 

804 content=content, 

805 data=data, 

806 files=files, 

807 json=json, 

808 params=params, 

809 headers=headers, 

810 cookies=cookies, 

811 timeout=timeout, 

812 extensions=extensions, 

813 ) 

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

815 

816 @contextmanager 

817 def stream( 

818 self, 

819 method: str, 

820 url: URLTypes, 

821 *, 

822 content: typing.Optional[RequestContent] = None, 

823 data: typing.Optional[RequestData] = None, 

824 files: typing.Optional[RequestFiles] = None, 

825 json: typing.Optional[typing.Any] = None, 

826 params: typing.Optional[QueryParamTypes] = None, 

827 headers: typing.Optional[HeaderTypes] = None, 

828 cookies: typing.Optional[CookieTypes] = None, 

829 auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT, 

830 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

831 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

832 extensions: typing.Optional[RequestExtensions] = None, 

833 ) -> typing.Iterator[Response]: 

834 """ 

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

836 instead of loading it into memory at once. 

837 

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

839 

840 See also: [Streaming Responses][0] 

841 

842 [0]: /quickstart#streaming-responses 

843 """ 

844 request = self.build_request( 

845 method=method, 

846 url=url, 

847 content=content, 

848 data=data, 

849 files=files, 

850 json=json, 

851 params=params, 

852 headers=headers, 

853 cookies=cookies, 

854 timeout=timeout, 

855 extensions=extensions, 

856 ) 

857 response = self.send( 

858 request=request, 

859 auth=auth, 

860 follow_redirects=follow_redirects, 

861 stream=True, 

862 ) 

863 try: 

864 yield response 

865 finally: 

866 response.close() 

867 

868 def send( 

869 self, 

870 request: Request, 

871 *, 

872 stream: bool = False, 

873 auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT, 

874 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

875 ) -> Response: 

876 """ 

877 Send a request. 

878 

879 The request is sent as-is, unmodified. 

880 

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

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

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

884 

885 See also: [Request instances][0] 

886 

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

888 """ 

889 if self._state == ClientState.CLOSED: 

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

891 

892 self._state = ClientState.OPENED 

893 follow_redirects = ( 

894 self.follow_redirects 

895 if isinstance(follow_redirects, UseClientDefault) 

896 else follow_redirects 

897 ) 

898 

899 auth = self._build_request_auth(request, auth) 

900 

901 response = self._send_handling_auth( 

902 request, 

903 auth=auth, 

904 follow_redirects=follow_redirects, 

905 history=[], 

906 ) 

907 try: 

908 if not stream: 

909 response.read() 

910 

911 return response 

912 

913 except BaseException as exc: 

914 response.close() 

915 raise exc 

916 

917 def _send_handling_auth( 

918 self, 

919 request: Request, 

920 auth: Auth, 

921 follow_redirects: bool, 

922 history: typing.List[Response], 

923 ) -> Response: 

924 auth_flow = auth.sync_auth_flow(request) 

925 try: 

926 request = next(auth_flow) 

927 

928 while True: 

929 response = self._send_handling_redirects( 

930 request, 

931 follow_redirects=follow_redirects, 

932 history=history, 

933 ) 

934 try: 

935 try: 

936 next_request = auth_flow.send(response) 

937 except StopIteration: 

938 return response 

939 

940 response.history = list(history) 

941 response.read() 

942 request = next_request 

943 history.append(response) 

944 

945 except BaseException as exc: 

946 response.close() 

947 raise exc 

948 finally: 

949 auth_flow.close() 

950 

951 def _send_handling_redirects( 

952 self, 

953 request: Request, 

954 follow_redirects: bool, 

955 history: typing.List[Response], 

956 ) -> Response: 

957 while True: 

958 if len(history) > self.max_redirects: 

959 raise TooManyRedirects( 

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

961 ) 

962 

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

964 hook(request) 

965 

966 response = self._send_single_request(request) 

967 try: 

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

969 hook(response) 

970 response.history = list(history) 

971 

972 if not response.has_redirect_location: 

973 return response 

974 

975 request = self._build_redirect_request(request, response) 

976 history = history + [response] 

977 

978 if follow_redirects: 

979 response.read() 

980 else: 

981 response.next_request = request 

982 return response 

983 

984 except BaseException as exc: 

985 response.close() 

986 raise exc 

987 

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

989 """ 

990 Sends a single request, without handling any redirections. 

991 """ 

992 transport = self._transport_for_url(request.url) 

993 timer = Timer() 

994 timer.sync_start() 

995 

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

997 raise RuntimeError( 

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

999 ) 

1000 

1001 with request_context(request=request): 

1002 response = transport.handle_request(request) 

1003 

1004 assert isinstance(response.stream, SyncByteStream) 

1005 

1006 response.request = request 

1007 response.stream = BoundSyncStream( 

1008 response.stream, response=response, timer=timer 

1009 ) 

1010 self.cookies.extract_cookies(response) 

1011 response.default_encoding = self._default_encoding 

1012 

1013 logger.info( 

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

1015 request.method, 

1016 request.url, 

1017 response.http_version, 

1018 response.status_code, 

1019 response.reason_phrase, 

1020 ) 

1021 

1022 return response 

1023 

1024 def get( 

1025 self, 

1026 url: URLTypes, 

1027 *, 

1028 params: typing.Optional[QueryParamTypes] = None, 

1029 headers: typing.Optional[HeaderTypes] = None, 

1030 cookies: typing.Optional[CookieTypes] = None, 

1031 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1032 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1033 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1034 extensions: typing.Optional[RequestExtensions] = None, 

1035 ) -> Response: 

1036 """ 

1037 Send a `GET` request. 

1038 

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

1040 """ 

1041 return self.request( 

1042 "GET", 

1043 url, 

1044 params=params, 

1045 headers=headers, 

1046 cookies=cookies, 

1047 auth=auth, 

1048 follow_redirects=follow_redirects, 

1049 timeout=timeout, 

1050 extensions=extensions, 

1051 ) 

1052 

1053 def options( 

1054 self, 

1055 url: URLTypes, 

1056 *, 

1057 params: typing.Optional[QueryParamTypes] = None, 

1058 headers: typing.Optional[HeaderTypes] = None, 

1059 cookies: typing.Optional[CookieTypes] = None, 

1060 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1061 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1062 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1063 extensions: typing.Optional[RequestExtensions] = None, 

1064 ) -> Response: 

1065 """ 

1066 Send an `OPTIONS` request. 

1067 

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

1069 """ 

1070 return self.request( 

1071 "OPTIONS", 

1072 url, 

1073 params=params, 

1074 headers=headers, 

1075 cookies=cookies, 

1076 auth=auth, 

1077 follow_redirects=follow_redirects, 

1078 timeout=timeout, 

1079 extensions=extensions, 

1080 ) 

1081 

1082 def head( 

1083 self, 

1084 url: URLTypes, 

1085 *, 

1086 params: typing.Optional[QueryParamTypes] = None, 

1087 headers: typing.Optional[HeaderTypes] = None, 

1088 cookies: typing.Optional[CookieTypes] = None, 

1089 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1090 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1091 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1092 extensions: typing.Optional[RequestExtensions] = None, 

1093 ) -> Response: 

1094 """ 

1095 Send a `HEAD` request. 

1096 

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

1098 """ 

1099 return self.request( 

1100 "HEAD", 

1101 url, 

1102 params=params, 

1103 headers=headers, 

1104 cookies=cookies, 

1105 auth=auth, 

1106 follow_redirects=follow_redirects, 

1107 timeout=timeout, 

1108 extensions=extensions, 

1109 ) 

1110 

1111 def post( 

1112 self, 

1113 url: URLTypes, 

1114 *, 

1115 content: typing.Optional[RequestContent] = None, 

1116 data: typing.Optional[RequestData] = None, 

1117 files: typing.Optional[RequestFiles] = None, 

1118 json: typing.Optional[typing.Any] = None, 

1119 params: typing.Optional[QueryParamTypes] = None, 

1120 headers: typing.Optional[HeaderTypes] = None, 

1121 cookies: typing.Optional[CookieTypes] = None, 

1122 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1123 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1124 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1125 extensions: typing.Optional[RequestExtensions] = None, 

1126 ) -> Response: 

1127 """ 

1128 Send a `POST` request. 

1129 

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

1131 """ 

1132 return self.request( 

1133 "POST", 

1134 url, 

1135 content=content, 

1136 data=data, 

1137 files=files, 

1138 json=json, 

1139 params=params, 

1140 headers=headers, 

1141 cookies=cookies, 

1142 auth=auth, 

1143 follow_redirects=follow_redirects, 

1144 timeout=timeout, 

1145 extensions=extensions, 

1146 ) 

1147 

1148 def put( 

1149 self, 

1150 url: URLTypes, 

1151 *, 

1152 content: typing.Optional[RequestContent] = None, 

1153 data: typing.Optional[RequestData] = None, 

1154 files: typing.Optional[RequestFiles] = None, 

1155 json: typing.Optional[typing.Any] = None, 

1156 params: typing.Optional[QueryParamTypes] = None, 

1157 headers: typing.Optional[HeaderTypes] = None, 

1158 cookies: typing.Optional[CookieTypes] = None, 

1159 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1160 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1161 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1162 extensions: typing.Optional[RequestExtensions] = None, 

1163 ) -> Response: 

1164 """ 

1165 Send a `PUT` request. 

1166 

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

1168 """ 

1169 return self.request( 

1170 "PUT", 

1171 url, 

1172 content=content, 

1173 data=data, 

1174 files=files, 

1175 json=json, 

1176 params=params, 

1177 headers=headers, 

1178 cookies=cookies, 

1179 auth=auth, 

1180 follow_redirects=follow_redirects, 

1181 timeout=timeout, 

1182 extensions=extensions, 

1183 ) 

1184 

1185 def patch( 

1186 self, 

1187 url: URLTypes, 

1188 *, 

1189 content: typing.Optional[RequestContent] = None, 

1190 data: typing.Optional[RequestData] = None, 

1191 files: typing.Optional[RequestFiles] = None, 

1192 json: typing.Optional[typing.Any] = None, 

1193 params: typing.Optional[QueryParamTypes] = None, 

1194 headers: typing.Optional[HeaderTypes] = None, 

1195 cookies: typing.Optional[CookieTypes] = None, 

1196 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1197 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1198 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1199 extensions: typing.Optional[RequestExtensions] = None, 

1200 ) -> Response: 

1201 """ 

1202 Send a `PATCH` request. 

1203 

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

1205 """ 

1206 return self.request( 

1207 "PATCH", 

1208 url, 

1209 content=content, 

1210 data=data, 

1211 files=files, 

1212 json=json, 

1213 params=params, 

1214 headers=headers, 

1215 cookies=cookies, 

1216 auth=auth, 

1217 follow_redirects=follow_redirects, 

1218 timeout=timeout, 

1219 extensions=extensions, 

1220 ) 

1221 

1222 def delete( 

1223 self, 

1224 url: URLTypes, 

1225 *, 

1226 params: typing.Optional[QueryParamTypes] = None, 

1227 headers: typing.Optional[HeaderTypes] = None, 

1228 cookies: typing.Optional[CookieTypes] = None, 

1229 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1230 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1231 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1232 extensions: typing.Optional[RequestExtensions] = None, 

1233 ) -> Response: 

1234 """ 

1235 Send a `DELETE` request. 

1236 

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

1238 """ 

1239 return self.request( 

1240 "DELETE", 

1241 url, 

1242 params=params, 

1243 headers=headers, 

1244 cookies=cookies, 

1245 auth=auth, 

1246 follow_redirects=follow_redirects, 

1247 timeout=timeout, 

1248 extensions=extensions, 

1249 ) 

1250 

1251 def close(self) -> None: 

1252 """ 

1253 Close transport and proxies. 

1254 """ 

1255 if self._state != ClientState.CLOSED: 

1256 self._state = ClientState.CLOSED 

1257 

1258 self._transport.close() 

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

1260 if transport is not None: 

1261 transport.close() 

1262 

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

1264 if self._state != ClientState.UNOPENED: 

1265 msg = { 

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

1267 ClientState.CLOSED: "Cannot reopen a client instance, once it has been closed.", 

1268 }[self._state] 

1269 raise RuntimeError(msg) 

1270 

1271 self._state = ClientState.OPENED 

1272 

1273 self._transport.__enter__() 

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

1275 if transport is not None: 

1276 transport.__enter__() 

1277 return self 

1278 

1279 def __exit__( 

1280 self, 

1281 exc_type: typing.Optional[typing.Type[BaseException]] = None, 

1282 exc_value: typing.Optional[BaseException] = None, 

1283 traceback: typing.Optional[TracebackType] = None, 

1284 ) -> None: 

1285 self._state = ClientState.CLOSED 

1286 

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

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

1289 if transport is not None: 

1290 transport.__exit__(exc_type, exc_value, traceback) 

1291 

1292 

1293class AsyncClient(BaseClient): 

1294 """ 

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

1296 cookie persistence, etc. 

1297 

1298 Usage: 

1299 

1300 ```python 

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

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

1303 ``` 

1304 

1305 **Parameters:** 

1306 

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

1308 requests. 

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

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

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

1312 sending requests. 

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

1314 sending requests. 

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

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

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

1318 (which will disable verification). 

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

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

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

1322 file, key file, password). 

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

1324 enabled. Defaults to `False`. 

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

1326 URLs. 

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

1328 requests. 

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

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

1331 that should be followed. 

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

1333 request URLs. 

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

1335 over the network. 

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

1337 rather than sending actual network requests. 

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

1339 variables for configuration. 

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

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

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

1343 """ 

1344 

1345 def __init__( 

1346 self, 

1347 *, 

1348 auth: typing.Optional[AuthTypes] = None, 

1349 params: typing.Optional[QueryParamTypes] = None, 

1350 headers: typing.Optional[HeaderTypes] = None, 

1351 cookies: typing.Optional[CookieTypes] = None, 

1352 verify: VerifyTypes = True, 

1353 cert: typing.Optional[CertTypes] = None, 

1354 http1: bool = True, 

1355 http2: bool = False, 

1356 proxies: typing.Optional[ProxiesTypes] = None, 

1357 mounts: typing.Optional[typing.Mapping[str, AsyncBaseTransport]] = None, 

1358 timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 

1359 follow_redirects: bool = False, 

1360 limits: Limits = DEFAULT_LIMITS, 

1361 max_redirects: int = DEFAULT_MAX_REDIRECTS, 

1362 event_hooks: typing.Optional[ 

1363 typing.Mapping[str, typing.List[typing.Callable[..., typing.Any]]] 

1364 ] = None, 

1365 base_url: URLTypes = "", 

1366 transport: typing.Optional[AsyncBaseTransport] = None, 

1367 app: typing.Optional[typing.Callable[..., typing.Any]] = None, 

1368 trust_env: bool = True, 

1369 default_encoding: typing.Union[str, typing.Callable[[bytes], str]] = "utf-8", 

1370 ): 

1371 super().__init__( 

1372 auth=auth, 

1373 params=params, 

1374 headers=headers, 

1375 cookies=cookies, 

1376 timeout=timeout, 

1377 follow_redirects=follow_redirects, 

1378 max_redirects=max_redirects, 

1379 event_hooks=event_hooks, 

1380 base_url=base_url, 

1381 trust_env=trust_env, 

1382 default_encoding=default_encoding, 

1383 ) 

1384 

1385 if http2: 

1386 try: 

1387 import h2 # noqa 

1388 except ImportError: # pragma: no cover 

1389 raise ImportError( 

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

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

1392 ) from None 

1393 

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

1395 proxy_map = self._get_proxy_map(proxies, allow_env_proxies) 

1396 

1397 self._transport = self._init_transport( 

1398 verify=verify, 

1399 cert=cert, 

1400 http1=http1, 

1401 http2=http2, 

1402 limits=limits, 

1403 transport=transport, 

1404 app=app, 

1405 trust_env=trust_env, 

1406 ) 

1407 

1408 self._mounts: typing.Dict[URLPattern, typing.Optional[AsyncBaseTransport]] = { 

1409 URLPattern(key): None 

1410 if proxy is None 

1411 else self._init_proxy_transport( 

1412 proxy, 

1413 verify=verify, 

1414 cert=cert, 

1415 http1=http1, 

1416 http2=http2, 

1417 limits=limits, 

1418 trust_env=trust_env, 

1419 ) 

1420 for key, proxy in proxy_map.items() 

1421 } 

1422 if mounts is not None: 

1423 self._mounts.update( 

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

1425 ) 

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

1427 

1428 def _init_transport( 

1429 self, 

1430 verify: VerifyTypes = True, 

1431 cert: typing.Optional[CertTypes] = None, 

1432 http1: bool = True, 

1433 http2: bool = False, 

1434 limits: Limits = DEFAULT_LIMITS, 

1435 transport: typing.Optional[AsyncBaseTransport] = None, 

1436 app: typing.Optional[typing.Callable[..., typing.Any]] = None, 

1437 trust_env: bool = True, 

1438 ) -> AsyncBaseTransport: 

1439 if transport is not None: 

1440 return transport 

1441 

1442 if app is not None: 

1443 return ASGITransport(app=app) 

1444 

1445 return AsyncHTTPTransport( 

1446 verify=verify, 

1447 cert=cert, 

1448 http1=http1, 

1449 http2=http2, 

1450 limits=limits, 

1451 trust_env=trust_env, 

1452 ) 

1453 

1454 def _init_proxy_transport( 

1455 self, 

1456 proxy: Proxy, 

1457 verify: VerifyTypes = True, 

1458 cert: typing.Optional[CertTypes] = None, 

1459 http1: bool = True, 

1460 http2: bool = False, 

1461 limits: Limits = DEFAULT_LIMITS, 

1462 trust_env: bool = True, 

1463 ) -> AsyncBaseTransport: 

1464 return AsyncHTTPTransport( 

1465 verify=verify, 

1466 cert=cert, 

1467 http2=http2, 

1468 limits=limits, 

1469 trust_env=trust_env, 

1470 proxy=proxy, 

1471 ) 

1472 

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

1474 """ 

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

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

1477 """ 

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

1479 if pattern.matches(url): 

1480 return self._transport if transport is None else transport 

1481 

1482 return self._transport 

1483 

1484 async def request( 

1485 self, 

1486 method: str, 

1487 url: URLTypes, 

1488 *, 

1489 content: typing.Optional[RequestContent] = None, 

1490 data: typing.Optional[RequestData] = None, 

1491 files: typing.Optional[RequestFiles] = None, 

1492 json: typing.Optional[typing.Any] = None, 

1493 params: typing.Optional[QueryParamTypes] = None, 

1494 headers: typing.Optional[HeaderTypes] = None, 

1495 cookies: typing.Optional[CookieTypes] = None, 

1496 auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT, 

1497 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1498 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1499 extensions: typing.Optional[RequestExtensions] = None, 

1500 ) -> Response: 

1501 """ 

1502 Build and send a request. 

1503 

1504 Equivalent to: 

1505 

1506 ```python 

1507 request = client.build_request(...) 

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

1509 ``` 

1510 

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

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

1513 are merged with client-level configuration. 

1514 

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

1516 """ 

1517 request = self.build_request( 

1518 method=method, 

1519 url=url, 

1520 content=content, 

1521 data=data, 

1522 files=files, 

1523 json=json, 

1524 params=params, 

1525 headers=headers, 

1526 cookies=cookies, 

1527 timeout=timeout, 

1528 extensions=extensions, 

1529 ) 

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

1531 

1532 @asynccontextmanager 

1533 async def stream( 

1534 self, 

1535 method: str, 

1536 url: URLTypes, 

1537 *, 

1538 content: typing.Optional[RequestContent] = None, 

1539 data: typing.Optional[RequestData] = None, 

1540 files: typing.Optional[RequestFiles] = None, 

1541 json: typing.Optional[typing.Any] = None, 

1542 params: typing.Optional[QueryParamTypes] = None, 

1543 headers: typing.Optional[HeaderTypes] = None, 

1544 cookies: typing.Optional[CookieTypes] = None, 

1545 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1546 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1547 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1548 extensions: typing.Optional[RequestExtensions] = None, 

1549 ) -> typing.AsyncIterator[Response]: 

1550 """ 

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

1552 instead of loading it into memory at once. 

1553 

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

1555 

1556 See also: [Streaming Responses][0] 

1557 

1558 [0]: /quickstart#streaming-responses 

1559 """ 

1560 request = self.build_request( 

1561 method=method, 

1562 url=url, 

1563 content=content, 

1564 data=data, 

1565 files=files, 

1566 json=json, 

1567 params=params, 

1568 headers=headers, 

1569 cookies=cookies, 

1570 timeout=timeout, 

1571 extensions=extensions, 

1572 ) 

1573 response = await self.send( 

1574 request=request, 

1575 auth=auth, 

1576 follow_redirects=follow_redirects, 

1577 stream=True, 

1578 ) 

1579 try: 

1580 yield response 

1581 finally: 

1582 await response.aclose() 

1583 

1584 async def send( 

1585 self, 

1586 request: Request, 

1587 *, 

1588 stream: bool = False, 

1589 auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT, 

1590 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1591 ) -> Response: 

1592 """ 

1593 Send a request. 

1594 

1595 The request is sent as-is, unmodified. 

1596 

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

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

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

1600 

1601 See also: [Request instances][0] 

1602 

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

1604 """ 

1605 if self._state == ClientState.CLOSED: 

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

1607 

1608 self._state = ClientState.OPENED 

1609 follow_redirects = ( 

1610 self.follow_redirects 

1611 if isinstance(follow_redirects, UseClientDefault) 

1612 else follow_redirects 

1613 ) 

1614 

1615 auth = self._build_request_auth(request, auth) 

1616 

1617 response = await self._send_handling_auth( 

1618 request, 

1619 auth=auth, 

1620 follow_redirects=follow_redirects, 

1621 history=[], 

1622 ) 

1623 try: 

1624 if not stream: 

1625 await response.aread() 

1626 

1627 return response 

1628 

1629 except BaseException as exc: # pragma: no cover 

1630 await response.aclose() 

1631 raise exc 

1632 

1633 async def _send_handling_auth( 

1634 self, 

1635 request: Request, 

1636 auth: Auth, 

1637 follow_redirects: bool, 

1638 history: typing.List[Response], 

1639 ) -> Response: 

1640 auth_flow = auth.async_auth_flow(request) 

1641 try: 

1642 request = await auth_flow.__anext__() 

1643 

1644 while True: 

1645 response = await self._send_handling_redirects( 

1646 request, 

1647 follow_redirects=follow_redirects, 

1648 history=history, 

1649 ) 

1650 try: 

1651 try: 

1652 next_request = await auth_flow.asend(response) 

1653 except StopAsyncIteration: 

1654 return response 

1655 

1656 response.history = list(history) 

1657 await response.aread() 

1658 request = next_request 

1659 history.append(response) 

1660 

1661 except BaseException as exc: 

1662 await response.aclose() 

1663 raise exc 

1664 finally: 

1665 await auth_flow.aclose() 

1666 

1667 async def _send_handling_redirects( 

1668 self, 

1669 request: Request, 

1670 follow_redirects: bool, 

1671 history: typing.List[Response], 

1672 ) -> Response: 

1673 while True: 

1674 if len(history) > self.max_redirects: 

1675 raise TooManyRedirects( 

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

1677 ) 

1678 

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

1680 await hook(request) 

1681 

1682 response = await self._send_single_request(request) 

1683 try: 

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

1685 await hook(response) 

1686 

1687 response.history = list(history) 

1688 

1689 if not response.has_redirect_location: 

1690 return response 

1691 

1692 request = self._build_redirect_request(request, response) 

1693 history = history + [response] 

1694 

1695 if follow_redirects: 

1696 await response.aread() 

1697 else: 

1698 response.next_request = request 

1699 return response 

1700 

1701 except BaseException as exc: 

1702 await response.aclose() 

1703 raise exc 

1704 

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

1706 """ 

1707 Sends a single request, without handling any redirections. 

1708 """ 

1709 transport = self._transport_for_url(request.url) 

1710 timer = Timer() 

1711 await timer.async_start() 

1712 

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

1714 raise RuntimeError( 

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

1716 ) 

1717 

1718 with request_context(request=request): 

1719 response = await transport.handle_async_request(request) 

1720 

1721 assert isinstance(response.stream, AsyncByteStream) 

1722 response.request = request 

1723 response.stream = BoundAsyncStream( 

1724 response.stream, response=response, timer=timer 

1725 ) 

1726 self.cookies.extract_cookies(response) 

1727 response.default_encoding = self._default_encoding 

1728 

1729 logger.info( 

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

1731 request.method, 

1732 request.url, 

1733 response.http_version, 

1734 response.status_code, 

1735 response.reason_phrase, 

1736 ) 

1737 

1738 return response 

1739 

1740 async def get( 

1741 self, 

1742 url: URLTypes, 

1743 *, 

1744 params: typing.Optional[QueryParamTypes] = None, 

1745 headers: typing.Optional[HeaderTypes] = None, 

1746 cookies: typing.Optional[CookieTypes] = None, 

1747 auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT, 

1748 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1749 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1750 extensions: typing.Optional[RequestExtensions] = None, 

1751 ) -> Response: 

1752 """ 

1753 Send a `GET` request. 

1754 

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

1756 """ 

1757 return await self.request( 

1758 "GET", 

1759 url, 

1760 params=params, 

1761 headers=headers, 

1762 cookies=cookies, 

1763 auth=auth, 

1764 follow_redirects=follow_redirects, 

1765 timeout=timeout, 

1766 extensions=extensions, 

1767 ) 

1768 

1769 async def options( 

1770 self, 

1771 url: URLTypes, 

1772 *, 

1773 params: typing.Optional[QueryParamTypes] = None, 

1774 headers: typing.Optional[HeaderTypes] = None, 

1775 cookies: typing.Optional[CookieTypes] = None, 

1776 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1777 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1778 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1779 extensions: typing.Optional[RequestExtensions] = None, 

1780 ) -> Response: 

1781 """ 

1782 Send an `OPTIONS` request. 

1783 

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

1785 """ 

1786 return await self.request( 

1787 "OPTIONS", 

1788 url, 

1789 params=params, 

1790 headers=headers, 

1791 cookies=cookies, 

1792 auth=auth, 

1793 follow_redirects=follow_redirects, 

1794 timeout=timeout, 

1795 extensions=extensions, 

1796 ) 

1797 

1798 async def head( 

1799 self, 

1800 url: URLTypes, 

1801 *, 

1802 params: typing.Optional[QueryParamTypes] = None, 

1803 headers: typing.Optional[HeaderTypes] = None, 

1804 cookies: typing.Optional[CookieTypes] = None, 

1805 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1806 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1807 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1808 extensions: typing.Optional[RequestExtensions] = None, 

1809 ) -> Response: 

1810 """ 

1811 Send a `HEAD` request. 

1812 

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

1814 """ 

1815 return await self.request( 

1816 "HEAD", 

1817 url, 

1818 params=params, 

1819 headers=headers, 

1820 cookies=cookies, 

1821 auth=auth, 

1822 follow_redirects=follow_redirects, 

1823 timeout=timeout, 

1824 extensions=extensions, 

1825 ) 

1826 

1827 async def post( 

1828 self, 

1829 url: URLTypes, 

1830 *, 

1831 content: typing.Optional[RequestContent] = None, 

1832 data: typing.Optional[RequestData] = None, 

1833 files: typing.Optional[RequestFiles] = None, 

1834 json: typing.Optional[typing.Any] = None, 

1835 params: typing.Optional[QueryParamTypes] = None, 

1836 headers: typing.Optional[HeaderTypes] = None, 

1837 cookies: typing.Optional[CookieTypes] = None, 

1838 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1839 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1840 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1841 extensions: typing.Optional[RequestExtensions] = None, 

1842 ) -> Response: 

1843 """ 

1844 Send a `POST` request. 

1845 

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

1847 """ 

1848 return await self.request( 

1849 "POST", 

1850 url, 

1851 content=content, 

1852 data=data, 

1853 files=files, 

1854 json=json, 

1855 params=params, 

1856 headers=headers, 

1857 cookies=cookies, 

1858 auth=auth, 

1859 follow_redirects=follow_redirects, 

1860 timeout=timeout, 

1861 extensions=extensions, 

1862 ) 

1863 

1864 async def put( 

1865 self, 

1866 url: URLTypes, 

1867 *, 

1868 content: typing.Optional[RequestContent] = None, 

1869 data: typing.Optional[RequestData] = None, 

1870 files: typing.Optional[RequestFiles] = None, 

1871 json: typing.Optional[typing.Any] = None, 

1872 params: typing.Optional[QueryParamTypes] = None, 

1873 headers: typing.Optional[HeaderTypes] = None, 

1874 cookies: typing.Optional[CookieTypes] = None, 

1875 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1876 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1877 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1878 extensions: typing.Optional[RequestExtensions] = None, 

1879 ) -> Response: 

1880 """ 

1881 Send a `PUT` request. 

1882 

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

1884 """ 

1885 return await self.request( 

1886 "PUT", 

1887 url, 

1888 content=content, 

1889 data=data, 

1890 files=files, 

1891 json=json, 

1892 params=params, 

1893 headers=headers, 

1894 cookies=cookies, 

1895 auth=auth, 

1896 follow_redirects=follow_redirects, 

1897 timeout=timeout, 

1898 extensions=extensions, 

1899 ) 

1900 

1901 async def patch( 

1902 self, 

1903 url: URLTypes, 

1904 *, 

1905 content: typing.Optional[RequestContent] = None, 

1906 data: typing.Optional[RequestData] = None, 

1907 files: typing.Optional[RequestFiles] = None, 

1908 json: typing.Optional[typing.Any] = None, 

1909 params: typing.Optional[QueryParamTypes] = None, 

1910 headers: typing.Optional[HeaderTypes] = None, 

1911 cookies: typing.Optional[CookieTypes] = None, 

1912 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1913 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1914 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1915 extensions: typing.Optional[RequestExtensions] = None, 

1916 ) -> Response: 

1917 """ 

1918 Send a `PATCH` request. 

1919 

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

1921 """ 

1922 return await self.request( 

1923 "PATCH", 

1924 url, 

1925 content=content, 

1926 data=data, 

1927 files=files, 

1928 json=json, 

1929 params=params, 

1930 headers=headers, 

1931 cookies=cookies, 

1932 auth=auth, 

1933 follow_redirects=follow_redirects, 

1934 timeout=timeout, 

1935 extensions=extensions, 

1936 ) 

1937 

1938 async def delete( 

1939 self, 

1940 url: URLTypes, 

1941 *, 

1942 params: typing.Optional[QueryParamTypes] = None, 

1943 headers: typing.Optional[HeaderTypes] = None, 

1944 cookies: typing.Optional[CookieTypes] = None, 

1945 auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1946 follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT, 

1947 timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT, 

1948 extensions: typing.Optional[RequestExtensions] = None, 

1949 ) -> Response: 

1950 """ 

1951 Send a `DELETE` request. 

1952 

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

1954 """ 

1955 return await self.request( 

1956 "DELETE", 

1957 url, 

1958 params=params, 

1959 headers=headers, 

1960 cookies=cookies, 

1961 auth=auth, 

1962 follow_redirects=follow_redirects, 

1963 timeout=timeout, 

1964 extensions=extensions, 

1965 ) 

1966 

1967 async def aclose(self) -> None: 

1968 """ 

1969 Close transport and proxies. 

1970 """ 

1971 if self._state != ClientState.CLOSED: 

1972 self._state = ClientState.CLOSED 

1973 

1974 await self._transport.aclose() 

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

1976 if proxy is not None: 

1977 await proxy.aclose() 

1978 

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

1980 if self._state != ClientState.UNOPENED: 

1981 msg = { 

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

1983 ClientState.CLOSED: "Cannot reopen a client instance, once it has been closed.", 

1984 }[self._state] 

1985 raise RuntimeError(msg) 

1986 

1987 self._state = ClientState.OPENED 

1988 

1989 await self._transport.__aenter__() 

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

1991 if proxy is not None: 

1992 await proxy.__aenter__() 

1993 return self 

1994 

1995 async def __aexit__( 

1996 self, 

1997 exc_type: typing.Optional[typing.Type[BaseException]] = None, 

1998 exc_value: typing.Optional[BaseException] = None, 

1999 traceback: typing.Optional[TracebackType] = None, 

2000 ) -> None: 

2001 self._state = ClientState.CLOSED 

2002 

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

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

2005 if proxy is not None: 

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