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

522 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:12 +0000

1import datetime 

2import enum 

3import typing 

4import warnings 

5from contextlib import asynccontextmanager, contextmanager 

6from types import TracebackType 

7 

8from .__version__ import __version__ 

9from ._auth import Auth, BasicAuth, FunctionAuth 

10from ._config import ( 

11 DEFAULT_LIMITS, 

12 DEFAULT_MAX_REDIRECTS, 

13 DEFAULT_TIMEOUT_CONFIG, 

14 Limits, 

15 Proxy, 

16 Timeout, 

17) 

18from ._decoders import SUPPORTED_DECODERS 

19from ._exceptions import ( 

20 InvalidURL, 

21 RemoteProtocolError, 

22 TooManyRedirects, 

23 request_context, 

24) 

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

26from ._status_codes import codes 

27from ._transports.asgi import ASGITransport 

28from ._transports.base import AsyncBaseTransport, BaseTransport 

29from ._transports.default import AsyncHTTPTransport, HTTPTransport 

30from ._transports.wsgi import WSGITransport 

31from ._types import ( 

32 AsyncByteStream, 

33 AuthTypes, 

34 CertTypes, 

35 CookieTypes, 

36 HeaderTypes, 

37 ProxiesTypes, 

38 QueryParamTypes, 

39 RequestContent, 

40 RequestData, 

41 RequestExtensions, 

42 RequestFiles, 

43 SyncByteStream, 

44 TimeoutTypes, 

45 URLTypes, 

46 VerifyTypes, 

47) 

48from ._urls import URL, QueryParams 

49from ._utils import ( 

50 NetRCInfo, 

51 Timer, 

52 URLPattern, 

53 get_environment_proxies, 

54 get_logger, 

55 is_https_redirect, 

56 same_origin, 

57) 

58 

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

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

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

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

63 

64 

65class UseClientDefault: 

66 """ 

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

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

69 to using `None`. 

70 

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

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

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

74 

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

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

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

78 ensure no timeout is used. 

79 

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

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

82 """ 

83 

84 

85USE_CLIENT_DEFAULT = UseClientDefault() 

86 

87 

88logger = get_logger(__name__) 

89 

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

91ACCEPT_ENCODING = ", ".join( 

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

93) 

94 

95 

96class ClientState(enum.Enum): 

97 # UNOPENED: 

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

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

100 UNOPENED = 1 

101 # OPENED: 

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

103 OPENED = 2 

104 # CLOSED: 

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

106 # been called explicitly. 

107 CLOSED = 3 

108 

109 

110class BoundSyncStream(SyncByteStream): 

111 """ 

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

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

114 """ 

115 

116 def __init__( 

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

118 ) -> None: 

119 self._stream = stream 

120 self._response = response 

121 self._timer = timer 

122 

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

124 for chunk in self._stream: 

125 yield chunk 

126 

127 def close(self) -> None: 

128 seconds = self._timer.sync_elapsed() 

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

130 self._stream.close() 

131 

132 

133class BoundAsyncStream(AsyncByteStream): 

134 """ 

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

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

137 """ 

138 

139 def __init__( 

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

141 ) -> None: 

142 self._stream = stream 

143 self._response = response 

144 self._timer = timer 

145 

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

147 async for chunk in self._stream: 

148 yield chunk 

149 

150 async def aclose(self) -> None: 

151 seconds = await self._timer.async_elapsed() 

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

153 await self._stream.aclose() 

154 

155 

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

157 

158 

159class BaseClient: 

160 def __init__( 

161 self, 

162 *, 

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

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

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

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

167 timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 

168 follow_redirects: bool = False, 

169 max_redirects: int = DEFAULT_MAX_REDIRECTS, 

170 event_hooks: typing.Optional[ 

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

172 ] = None, 

173 base_url: URLTypes = "", 

174 trust_env: bool = True, 

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

176 ): 

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._netrc = NetRCInfo() 

195 self._state = ClientState.UNOPENED 

196 

197 @property 

198 def is_closed(self) -> bool: 

199 """ 

200 Check if the client being closed 

201 """ 

202 return self._state == ClientState.CLOSED 

203 

204 @property 

205 def trust_env(self) -> bool: 

206 return self._trust_env 

207 

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

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

210 return url 

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

212 

213 def _get_proxy_map( 

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

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

216 if proxies is None: 

217 if allow_env_proxies: 

218 return { 

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

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

221 } 

222 return {} 

223 if isinstance(proxies, dict): 

224 new_proxies = {} 

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

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

227 new_proxies[str(key)] = proxy 

228 return new_proxies 

229 else: 

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

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

232 

233 @property 

234 def timeout(self) -> Timeout: 

235 return self._timeout 

236 

237 @timeout.setter 

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

239 self._timeout = Timeout(timeout) 

240 

241 @property 

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

243 return self._event_hooks 

244 

245 @event_hooks.setter 

246 def event_hooks( 

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

248 ) -> None: 

249 self._event_hooks = { 

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

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

252 } 

253 

254 @property 

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

256 """ 

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

258 

259 See also [Authentication][0]. 

260 

261 [0]: /quickstart/#authentication 

262 """ 

263 return self._auth 

264 

265 @auth.setter 

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

267 self._auth = self._build_auth(auth) 

268 

269 @property 

270 def base_url(self) -> URL: 

271 """ 

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

273 """ 

274 return self._base_url 

275 

276 @base_url.setter 

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

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

279 

280 @property 

281 def headers(self) -> Headers: 

282 """ 

283 HTTP headers to include when sending requests. 

284 """ 

285 return self._headers 

286 

287 @headers.setter 

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

289 client_headers = Headers( 

290 { 

291 b"Accept": b"*/*", 

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

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

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

295 } 

296 ) 

297 client_headers.update(headers) 

298 self._headers = client_headers 

299 

300 @property 

301 def cookies(self) -> Cookies: 

302 """ 

303 Cookie values to include when sending requests. 

304 """ 

305 return self._cookies 

306 

307 @cookies.setter 

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

309 self._cookies = Cookies(cookies) 

310 

311 @property 

312 def params(self) -> QueryParams: 

313 """ 

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

315 """ 

316 return self._params 

317 

318 @params.setter 

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

320 self._params = QueryParams(params) 

321 

322 def build_request( 

323 self, 

324 method: str, 

325 url: URLTypes, 

326 *, 

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

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

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

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

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

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

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

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

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

336 ) -> Request: 

337 """ 

338 Build and return a request instance. 

339 

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

341 are merged with any values set on the client. 

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

343 

344 See also: [Request instances][0] 

345 

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

347 """ 

348 url = self._merge_url(url) 

349 headers = self._merge_headers(headers) 

350 cookies = self._merge_cookies(cookies) 

351 params = self._merge_queryparams(params) 

352 extensions = {} if extensions is None else extensions 

353 if "timeout" not in extensions: 

354 timeout = ( 

355 self.timeout 

356 if isinstance(timeout, UseClientDefault) 

357 else Timeout(timeout) 

358 ) 

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

360 return Request( 

361 method, 

362 url, 

363 content=content, 

364 data=data, 

365 files=files, 

366 json=json, 

367 params=params, 

368 headers=headers, 

369 cookies=cookies, 

370 extensions=extensions, 

371 ) 

372 

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

374 """ 

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

376 to create the URL used for the outgoing request. 

377 """ 

378 merge_url = URL(url) 

379 if merge_url.is_relative_url: 

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

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

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

383 # 

384 # So, eg... 

385 # 

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

387 # >>> client.base_url 

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

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

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

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

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

393 return merge_url 

394 

395 def _merge_cookies( 

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

397 ) -> typing.Optional[CookieTypes]: 

398 """ 

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

400 to create the cookies used for the outgoing request. 

401 """ 

402 if cookies or self.cookies: 

403 merged_cookies = Cookies(self.cookies) 

404 merged_cookies.update(cookies) 

405 return merged_cookies 

406 return cookies 

407 

408 def _merge_headers( 

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

410 ) -> typing.Optional[HeaderTypes]: 

411 """ 

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

413 to create the headers used for the outgoing request. 

414 """ 

415 merged_headers = Headers(self.headers) 

416 merged_headers.update(headers) 

417 return merged_headers 

418 

419 def _merge_queryparams( 

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

421 ) -> typing.Optional[QueryParamTypes]: 

422 """ 

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

424 to create the queryparams used for the outgoing request. 

425 """ 

426 if params or self.params: 

427 merged_queryparams = QueryParams(self.params) 

428 return merged_queryparams.merge(params) 

429 return params 

430 

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

432 if auth is None: 

433 return None 

434 elif isinstance(auth, tuple): 

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

436 elif isinstance(auth, Auth): 

437 return auth 

438 elif callable(auth): 

439 return FunctionAuth(func=auth) 

440 else: 

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

442 

443 def _build_request_auth( 

444 self, 

445 request: Request, 

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

447 ) -> Auth: 

448 auth = ( 

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

450 ) 

451 

452 if auth is not None: 

453 return auth 

454 

455 username, password = request.url.username, request.url.password 

456 if username or password: 

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

458 

459 if self.trust_env and "Authorization" not in request.headers: 

460 credentials = self._netrc.get_credentials(request.url.host) 

461 if credentials is not None: 

462 return BasicAuth(username=credentials[0], password=credentials[1]) 

463 

464 return Auth() 

465 

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

467 """ 

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

469 should be used to effect the redirect. 

470 """ 

471 method = self._redirect_method(request, response) 

472 url = self._redirect_url(request, response) 

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

474 stream = self._redirect_stream(request, method) 

475 cookies = Cookies(self.cookies) 

476 return Request( 

477 method=method, 

478 url=url, 

479 headers=headers, 

480 cookies=cookies, 

481 stream=stream, 

482 extensions=request.extensions, 

483 ) 

484 

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

486 """ 

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

488 based on certain specs or browser behavior. 

489 """ 

490 method = request.method 

491 

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

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

494 method = "GET" 

495 

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

497 # Turn 302s into GETs. 

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

499 method = "GET" 

500 

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

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

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

504 method = "GET" 

505 

506 return method 

507 

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

509 """ 

510 Return the URL for the redirect to follow. 

511 """ 

512 location = response.headers["Location"] 

513 

514 try: 

515 url = URL(location) 

516 except InvalidURL as exc: 

517 raise RemoteProtocolError( 

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

519 ) from None 

520 

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

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

523 if url.scheme and not url.host: 

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

525 

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

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

528 if url.is_relative_url: 

529 url = request.url.join(url) 

530 

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

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

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

534 

535 return url 

536 

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

538 """ 

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

540 """ 

541 headers = Headers(request.headers) 

542 

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

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

545 # Strip Authorization headers when responses are redirected 

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

547 headers.pop("Authorization", None) 

548 

549 # Update the Host header. 

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

551 

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

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

554 # are only relevant to the request body. 

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

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

557 

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

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

560 headers.pop("Cookie", None) 

561 

562 return headers 

563 

564 def _redirect_stream( 

565 self, request: Request, method: str 

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

567 """ 

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

569 """ 

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

571 return None 

572 

573 return request.stream 

574 

575 

576class Client(BaseClient): 

577 """ 

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

579 

580 It can be shared between threads. 

581 

582 Usage: 

583 

584 ```python 

585 >>> client = httpx.Client() 

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

587 ``` 

588 

589 **Parameters:** 

590 

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

592 requests. 

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

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

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

596 sending requests. 

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

598 sending requests. 

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

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

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

602 (which will disable verification). 

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

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

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

606 file, key file, password). 

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

608 URLs. 

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

610 requests. 

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

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

613 that should be followed. 

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

615 request URLs. 

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

617 over the network. 

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

619 rather than sending actual network requests. 

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

621 variables for configuration. 

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

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

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

625 """ 

626 

627 def __init__( 

628 self, 

629 *, 

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

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

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

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

634 verify: VerifyTypes = True, 

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

636 http1: bool = True, 

637 http2: bool = False, 

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

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

640 timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 

641 follow_redirects: bool = False, 

642 limits: Limits = DEFAULT_LIMITS, 

643 max_redirects: int = DEFAULT_MAX_REDIRECTS, 

644 event_hooks: typing.Optional[ 

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

646 ] = None, 

647 base_url: URLTypes = "", 

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

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

650 trust_env: bool = True, 

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

652 ): 

653 super().__init__( 

654 auth=auth, 

655 params=params, 

656 headers=headers, 

657 cookies=cookies, 

658 timeout=timeout, 

659 follow_redirects=follow_redirects, 

660 max_redirects=max_redirects, 

661 event_hooks=event_hooks, 

662 base_url=base_url, 

663 trust_env=trust_env, 

664 default_encoding=default_encoding, 

665 ) 

666 

667 if http2: 

668 try: 

669 import h2 # noqa 

670 except ImportError: # pragma: no cover 

671 raise ImportError( 

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

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

674 ) from None 

675 

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

677 proxy_map = self._get_proxy_map(proxies, allow_env_proxies) 

678 

679 self._transport = self._init_transport( 

680 verify=verify, 

681 cert=cert, 

682 http1=http1, 

683 http2=http2, 

684 limits=limits, 

685 transport=transport, 

686 app=app, 

687 trust_env=trust_env, 

688 ) 

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

690 URLPattern(key): None 

691 if proxy is None 

692 else self._init_proxy_transport( 

693 proxy, 

694 verify=verify, 

695 cert=cert, 

696 http1=http1, 

697 http2=http2, 

698 limits=limits, 

699 trust_env=trust_env, 

700 ) 

701 for key, proxy in proxy_map.items() 

702 } 

703 if mounts is not None: 

704 self._mounts.update( 

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

706 ) 

707 

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

709 

710 def _init_transport( 

711 self, 

712 verify: VerifyTypes = True, 

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

714 http1: bool = True, 

715 http2: bool = False, 

716 limits: Limits = DEFAULT_LIMITS, 

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

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

719 trust_env: bool = True, 

720 ) -> BaseTransport: 

721 if transport is not None: 

722 return transport 

723 

724 if app is not None: 

725 return WSGITransport(app=app) 

726 

727 return HTTPTransport( 

728 verify=verify, 

729 cert=cert, 

730 http1=http1, 

731 http2=http2, 

732 limits=limits, 

733 trust_env=trust_env, 

734 ) 

735 

736 def _init_proxy_transport( 

737 self, 

738 proxy: Proxy, 

739 verify: VerifyTypes = True, 

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

741 http1: bool = True, 

742 http2: bool = False, 

743 limits: Limits = DEFAULT_LIMITS, 

744 trust_env: bool = True, 

745 ) -> BaseTransport: 

746 return HTTPTransport( 

747 verify=verify, 

748 cert=cert, 

749 http1=http1, 

750 http2=http2, 

751 limits=limits, 

752 trust_env=trust_env, 

753 proxy=proxy, 

754 ) 

755 

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

757 """ 

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

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

760 """ 

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

762 if pattern.matches(url): 

763 return self._transport if transport is None else transport 

764 

765 return self._transport 

766 

767 def request( 

768 self, 

769 method: str, 

770 url: URLTypes, 

771 *, 

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

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

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

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

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

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

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

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

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

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

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

783 ) -> Response: 

784 """ 

785 Build and send a request. 

786 

787 Equivalent to: 

788 

789 ```python 

790 request = client.build_request(...) 

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

792 ``` 

793 

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

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

796 are merged with client-level configuration. 

797 

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

799 """ 

800 if cookies is not None: 

801 message = ( 

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

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

804 "cookies directly on the client instance instead." 

805 ) 

806 warnings.warn(message, DeprecationWarning) 

807 

808 request = self.build_request( 

809 method=method, 

810 url=url, 

811 content=content, 

812 data=data, 

813 files=files, 

814 json=json, 

815 params=params, 

816 headers=headers, 

817 cookies=cookies, 

818 timeout=timeout, 

819 extensions=extensions, 

820 ) 

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

822 

823 @contextmanager 

824 def stream( 

825 self, 

826 method: str, 

827 url: URLTypes, 

828 *, 

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

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

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

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

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

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

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

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

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

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

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

840 ) -> typing.Iterator[Response]: 

841 """ 

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

843 instead of loading it into memory at once. 

844 

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

846 

847 See also: [Streaming Responses][0] 

848 

849 [0]: /quickstart#streaming-responses 

850 """ 

851 request = self.build_request( 

852 method=method, 

853 url=url, 

854 content=content, 

855 data=data, 

856 files=files, 

857 json=json, 

858 params=params, 

859 headers=headers, 

860 cookies=cookies, 

861 timeout=timeout, 

862 extensions=extensions, 

863 ) 

864 response = self.send( 

865 request=request, 

866 auth=auth, 

867 follow_redirects=follow_redirects, 

868 stream=True, 

869 ) 

870 try: 

871 yield response 

872 finally: 

873 response.close() 

874 

875 def send( 

876 self, 

877 request: Request, 

878 *, 

879 stream: bool = False, 

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

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

882 ) -> Response: 

883 """ 

884 Send a request. 

885 

886 The request is sent as-is, unmodified. 

887 

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

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

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

891 

892 See also: [Request instances][0] 

893 

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

895 """ 

896 if self._state == ClientState.CLOSED: 

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

898 

899 self._state = ClientState.OPENED 

900 follow_redirects = ( 

901 self.follow_redirects 

902 if isinstance(follow_redirects, UseClientDefault) 

903 else follow_redirects 

904 ) 

905 

906 auth = self._build_request_auth(request, auth) 

907 

908 response = self._send_handling_auth( 

909 request, 

910 auth=auth, 

911 follow_redirects=follow_redirects, 

912 history=[], 

913 ) 

914 try: 

915 if not stream: 

916 response.read() 

917 

918 return response 

919 

920 except BaseException as exc: 

921 response.close() 

922 raise exc 

923 

924 def _send_handling_auth( 

925 self, 

926 request: Request, 

927 auth: Auth, 

928 follow_redirects: bool, 

929 history: typing.List[Response], 

930 ) -> Response: 

931 auth_flow = auth.sync_auth_flow(request) 

932 try: 

933 request = next(auth_flow) 

934 

935 while True: 

936 response = self._send_handling_redirects( 

937 request, 

938 follow_redirects=follow_redirects, 

939 history=history, 

940 ) 

941 try: 

942 try: 

943 next_request = auth_flow.send(response) 

944 except StopIteration: 

945 return response 

946 

947 response.history = list(history) 

948 response.read() 

949 request = next_request 

950 history.append(response) 

951 

952 except BaseException as exc: 

953 response.close() 

954 raise exc 

955 finally: 

956 auth_flow.close() 

957 

958 def _send_handling_redirects( 

959 self, 

960 request: Request, 

961 follow_redirects: bool, 

962 history: typing.List[Response], 

963 ) -> Response: 

964 while True: 

965 if len(history) > self.max_redirects: 

966 raise TooManyRedirects( 

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

968 ) 

969 

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

971 hook(request) 

972 

973 response = self._send_single_request(request) 

974 try: 

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

976 hook(response) 

977 response.history = list(history) 

978 

979 if not response.has_redirect_location: 

980 return response 

981 

982 request = self._build_redirect_request(request, response) 

983 history = history + [response] 

984 

985 if follow_redirects: 

986 response.read() 

987 else: 

988 response.next_request = request 

989 return response 

990 

991 except BaseException as exc: 

992 response.close() 

993 raise exc 

994 

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

996 """ 

997 Sends a single request, without handling any redirections. 

998 """ 

999 transport = self._transport_for_url(request.url) 

1000 timer = Timer() 

1001 timer.sync_start() 

1002 

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

1004 raise RuntimeError( 

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

1006 ) 

1007 

1008 with request_context(request=request): 

1009 response = transport.handle_request(request) 

1010 

1011 assert isinstance(response.stream, SyncByteStream) 

1012 

1013 response.request = request 

1014 response.stream = BoundSyncStream( 

1015 response.stream, response=response, timer=timer 

1016 ) 

1017 self.cookies.extract_cookies(response) 

1018 response.default_encoding = self._default_encoding 

1019 

1020 status = f"{response.status_code} {response.reason_phrase}" 

1021 response_line = f"{response.http_version} {status}" 

1022 logger.debug( 

1023 'HTTP Request: %s %s "%s"', request.method, request.url, response_line 

1024 ) 

1025 

1026 return response 

1027 

1028 def get( 

1029 self, 

1030 url: URLTypes, 

1031 *, 

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

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

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

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

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

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

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

1039 ) -> Response: 

1040 """ 

1041 Send a `GET` request. 

1042 

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

1044 """ 

1045 return self.request( 

1046 "GET", 

1047 url, 

1048 params=params, 

1049 headers=headers, 

1050 cookies=cookies, 

1051 auth=auth, 

1052 follow_redirects=follow_redirects, 

1053 timeout=timeout, 

1054 extensions=extensions, 

1055 ) 

1056 

1057 def options( 

1058 self, 

1059 url: URLTypes, 

1060 *, 

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

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

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

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

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

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

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

1068 ) -> Response: 

1069 """ 

1070 Send an `OPTIONS` request. 

1071 

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

1073 """ 

1074 return self.request( 

1075 "OPTIONS", 

1076 url, 

1077 params=params, 

1078 headers=headers, 

1079 cookies=cookies, 

1080 auth=auth, 

1081 follow_redirects=follow_redirects, 

1082 timeout=timeout, 

1083 extensions=extensions, 

1084 ) 

1085 

1086 def head( 

1087 self, 

1088 url: URLTypes, 

1089 *, 

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

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

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

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

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

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

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

1097 ) -> Response: 

1098 """ 

1099 Send a `HEAD` request. 

1100 

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

1102 """ 

1103 return self.request( 

1104 "HEAD", 

1105 url, 

1106 params=params, 

1107 headers=headers, 

1108 cookies=cookies, 

1109 auth=auth, 

1110 follow_redirects=follow_redirects, 

1111 timeout=timeout, 

1112 extensions=extensions, 

1113 ) 

1114 

1115 def post( 

1116 self, 

1117 url: URLTypes, 

1118 *, 

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

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

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

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

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

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

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

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

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

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

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

1130 ) -> Response: 

1131 """ 

1132 Send a `POST` request. 

1133 

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

1135 """ 

1136 return self.request( 

1137 "POST", 

1138 url, 

1139 content=content, 

1140 data=data, 

1141 files=files, 

1142 json=json, 

1143 params=params, 

1144 headers=headers, 

1145 cookies=cookies, 

1146 auth=auth, 

1147 follow_redirects=follow_redirects, 

1148 timeout=timeout, 

1149 extensions=extensions, 

1150 ) 

1151 

1152 def put( 

1153 self, 

1154 url: URLTypes, 

1155 *, 

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

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

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

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

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

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

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

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

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

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

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

1167 ) -> Response: 

1168 """ 

1169 Send a `PUT` request. 

1170 

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

1172 """ 

1173 return self.request( 

1174 "PUT", 

1175 url, 

1176 content=content, 

1177 data=data, 

1178 files=files, 

1179 json=json, 

1180 params=params, 

1181 headers=headers, 

1182 cookies=cookies, 

1183 auth=auth, 

1184 follow_redirects=follow_redirects, 

1185 timeout=timeout, 

1186 extensions=extensions, 

1187 ) 

1188 

1189 def patch( 

1190 self, 

1191 url: URLTypes, 

1192 *, 

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

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

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

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

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

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

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

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

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

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

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

1204 ) -> Response: 

1205 """ 

1206 Send a `PATCH` request. 

1207 

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

1209 """ 

1210 return self.request( 

1211 "PATCH", 

1212 url, 

1213 content=content, 

1214 data=data, 

1215 files=files, 

1216 json=json, 

1217 params=params, 

1218 headers=headers, 

1219 cookies=cookies, 

1220 auth=auth, 

1221 follow_redirects=follow_redirects, 

1222 timeout=timeout, 

1223 extensions=extensions, 

1224 ) 

1225 

1226 def delete( 

1227 self, 

1228 url: URLTypes, 

1229 *, 

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

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

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

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

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

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

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

1237 ) -> Response: 

1238 """ 

1239 Send a `DELETE` request. 

1240 

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

1242 """ 

1243 return self.request( 

1244 "DELETE", 

1245 url, 

1246 params=params, 

1247 headers=headers, 

1248 cookies=cookies, 

1249 auth=auth, 

1250 follow_redirects=follow_redirects, 

1251 timeout=timeout, 

1252 extensions=extensions, 

1253 ) 

1254 

1255 def close(self) -> None: 

1256 """ 

1257 Close transport and proxies. 

1258 """ 

1259 if self._state != ClientState.CLOSED: 

1260 self._state = ClientState.CLOSED 

1261 

1262 self._transport.close() 

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

1264 if transport is not None: 

1265 transport.close() 

1266 

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

1268 if self._state != ClientState.UNOPENED: 

1269 msg = { 

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

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

1272 }[self._state] 

1273 raise RuntimeError(msg) 

1274 

1275 self._state = ClientState.OPENED 

1276 

1277 self._transport.__enter__() 

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

1279 if transport is not None: 

1280 transport.__enter__() 

1281 return self 

1282 

1283 def __exit__( 

1284 self, 

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

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

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

1288 ) -> None: 

1289 self._state = ClientState.CLOSED 

1290 

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

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

1293 if transport is not None: 

1294 transport.__exit__(exc_type, exc_value, traceback) 

1295 

1296 

1297class AsyncClient(BaseClient): 

1298 """ 

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

1300 cookie persistence, etc. 

1301 

1302 Usage: 

1303 

1304 ```python 

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

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

1307 ``` 

1308 

1309 **Parameters:** 

1310 

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

1312 requests. 

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

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

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

1316 sending requests. 

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

1318 sending requests. 

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

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

1321 a path to an SSL certificate file, or `False` (disable verification). 

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

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

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

1325 file, key file, password). 

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

1327 enabled. Defaults to `False`. 

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

1329 URLs. 

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

1331 requests. 

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

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

1334 that should be followed. 

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

1336 request URLs. 

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

1338 over the network. 

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

1340 rather than sending actual network requests. 

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

1342 variables for configuration. 

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

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

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

1346 """ 

1347 

1348 def __init__( 

1349 self, 

1350 *, 

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

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

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

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

1355 verify: VerifyTypes = True, 

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

1357 http1: bool = True, 

1358 http2: bool = False, 

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

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

1361 timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 

1362 follow_redirects: bool = False, 

1363 limits: Limits = DEFAULT_LIMITS, 

1364 max_redirects: int = DEFAULT_MAX_REDIRECTS, 

1365 event_hooks: typing.Optional[ 

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

1367 ] = None, 

1368 base_url: URLTypes = "", 

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

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

1371 trust_env: bool = True, 

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

1373 ): 

1374 super().__init__( 

1375 auth=auth, 

1376 params=params, 

1377 headers=headers, 

1378 cookies=cookies, 

1379 timeout=timeout, 

1380 follow_redirects=follow_redirects, 

1381 max_redirects=max_redirects, 

1382 event_hooks=event_hooks, 

1383 base_url=base_url, 

1384 trust_env=trust_env, 

1385 default_encoding=default_encoding, 

1386 ) 

1387 

1388 if http2: 

1389 try: 

1390 import h2 # noqa 

1391 except ImportError: # pragma: no cover 

1392 raise ImportError( 

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

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

1395 ) from None 

1396 

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

1398 proxy_map = self._get_proxy_map(proxies, allow_env_proxies) 

1399 

1400 self._transport = self._init_transport( 

1401 verify=verify, 

1402 cert=cert, 

1403 http1=http1, 

1404 http2=http2, 

1405 limits=limits, 

1406 transport=transport, 

1407 app=app, 

1408 trust_env=trust_env, 

1409 ) 

1410 

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

1412 URLPattern(key): None 

1413 if proxy is None 

1414 else self._init_proxy_transport( 

1415 proxy, 

1416 verify=verify, 

1417 cert=cert, 

1418 http1=http1, 

1419 http2=http2, 

1420 limits=limits, 

1421 trust_env=trust_env, 

1422 ) 

1423 for key, proxy in proxy_map.items() 

1424 } 

1425 if mounts is not None: 

1426 self._mounts.update( 

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

1428 ) 

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

1430 

1431 def _init_transport( 

1432 self, 

1433 verify: VerifyTypes = True, 

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

1435 http1: bool = True, 

1436 http2: bool = False, 

1437 limits: Limits = DEFAULT_LIMITS, 

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

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

1440 trust_env: bool = True, 

1441 ) -> AsyncBaseTransport: 

1442 if transport is not None: 

1443 return transport 

1444 

1445 if app is not None: 

1446 return ASGITransport(app=app) 

1447 

1448 return AsyncHTTPTransport( 

1449 verify=verify, 

1450 cert=cert, 

1451 http1=http1, 

1452 http2=http2, 

1453 limits=limits, 

1454 trust_env=trust_env, 

1455 ) 

1456 

1457 def _init_proxy_transport( 

1458 self, 

1459 proxy: Proxy, 

1460 verify: VerifyTypes = True, 

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

1462 http1: bool = True, 

1463 http2: bool = False, 

1464 limits: Limits = DEFAULT_LIMITS, 

1465 trust_env: bool = True, 

1466 ) -> AsyncBaseTransport: 

1467 return AsyncHTTPTransport( 

1468 verify=verify, 

1469 cert=cert, 

1470 http2=http2, 

1471 limits=limits, 

1472 trust_env=trust_env, 

1473 proxy=proxy, 

1474 ) 

1475 

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

1477 """ 

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

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

1480 """ 

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

1482 if pattern.matches(url): 

1483 return self._transport if transport is None else transport 

1484 

1485 return self._transport 

1486 

1487 async def request( 

1488 self, 

1489 method: str, 

1490 url: URLTypes, 

1491 *, 

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

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

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

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

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

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

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

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

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

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

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

1503 ) -> Response: 

1504 """ 

1505 Build and send a request. 

1506 

1507 Equivalent to: 

1508 

1509 ```python 

1510 request = client.build_request(...) 

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

1512 ``` 

1513 

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

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

1516 are merged with client-level configuration. 

1517 

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

1519 """ 

1520 request = self.build_request( 

1521 method=method, 

1522 url=url, 

1523 content=content, 

1524 data=data, 

1525 files=files, 

1526 json=json, 

1527 params=params, 

1528 headers=headers, 

1529 cookies=cookies, 

1530 timeout=timeout, 

1531 extensions=extensions, 

1532 ) 

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

1534 

1535 @asynccontextmanager 

1536 async def stream( 

1537 self, 

1538 method: str, 

1539 url: URLTypes, 

1540 *, 

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

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

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

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

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

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

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

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

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

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

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

1552 ) -> typing.AsyncIterator[Response]: 

1553 """ 

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

1555 instead of loading it into memory at once. 

1556 

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

1558 

1559 See also: [Streaming Responses][0] 

1560 

1561 [0]: /quickstart#streaming-responses 

1562 """ 

1563 request = self.build_request( 

1564 method=method, 

1565 url=url, 

1566 content=content, 

1567 data=data, 

1568 files=files, 

1569 json=json, 

1570 params=params, 

1571 headers=headers, 

1572 cookies=cookies, 

1573 timeout=timeout, 

1574 extensions=extensions, 

1575 ) 

1576 response = await self.send( 

1577 request=request, 

1578 auth=auth, 

1579 follow_redirects=follow_redirects, 

1580 stream=True, 

1581 ) 

1582 try: 

1583 yield response 

1584 finally: 

1585 await response.aclose() 

1586 

1587 async def send( 

1588 self, 

1589 request: Request, 

1590 *, 

1591 stream: bool = False, 

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

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

1594 ) -> Response: 

1595 """ 

1596 Send a request. 

1597 

1598 The request is sent as-is, unmodified. 

1599 

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

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

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

1603 

1604 See also: [Request instances][0] 

1605 

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

1607 """ 

1608 if self._state == ClientState.CLOSED: 

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

1610 

1611 self._state = ClientState.OPENED 

1612 follow_redirects = ( 

1613 self.follow_redirects 

1614 if isinstance(follow_redirects, UseClientDefault) 

1615 else follow_redirects 

1616 ) 

1617 

1618 auth = self._build_request_auth(request, auth) 

1619 

1620 response = await self._send_handling_auth( 

1621 request, 

1622 auth=auth, 

1623 follow_redirects=follow_redirects, 

1624 history=[], 

1625 ) 

1626 try: 

1627 if not stream: 

1628 await response.aread() 

1629 

1630 return response 

1631 

1632 except BaseException as exc: # pragma: no cover 

1633 await response.aclose() 

1634 raise exc 

1635 

1636 async def _send_handling_auth( 

1637 self, 

1638 request: Request, 

1639 auth: Auth, 

1640 follow_redirects: bool, 

1641 history: typing.List[Response], 

1642 ) -> Response: 

1643 auth_flow = auth.async_auth_flow(request) 

1644 try: 

1645 request = await auth_flow.__anext__() 

1646 

1647 while True: 

1648 response = await self._send_handling_redirects( 

1649 request, 

1650 follow_redirects=follow_redirects, 

1651 history=history, 

1652 ) 

1653 try: 

1654 try: 

1655 next_request = await auth_flow.asend(response) 

1656 except StopAsyncIteration: 

1657 return response 

1658 

1659 response.history = list(history) 

1660 await response.aread() 

1661 request = next_request 

1662 history.append(response) 

1663 

1664 except BaseException as exc: 

1665 await response.aclose() 

1666 raise exc 

1667 finally: 

1668 await auth_flow.aclose() 

1669 

1670 async def _send_handling_redirects( 

1671 self, 

1672 request: Request, 

1673 follow_redirects: bool, 

1674 history: typing.List[Response], 

1675 ) -> Response: 

1676 while True: 

1677 if len(history) > self.max_redirects: 

1678 raise TooManyRedirects( 

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

1680 ) 

1681 

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

1683 await hook(request) 

1684 

1685 response = await self._send_single_request(request) 

1686 try: 

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

1688 await hook(response) 

1689 

1690 response.history = list(history) 

1691 

1692 if not response.has_redirect_location: 

1693 return response 

1694 

1695 request = self._build_redirect_request(request, response) 

1696 history = history + [response] 

1697 

1698 if follow_redirects: 

1699 await response.aread() 

1700 else: 

1701 response.next_request = request 

1702 return response 

1703 

1704 except BaseException as exc: 

1705 await response.aclose() 

1706 raise exc 

1707 

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

1709 """ 

1710 Sends a single request, without handling any redirections. 

1711 """ 

1712 transport = self._transport_for_url(request.url) 

1713 timer = Timer() 

1714 await timer.async_start() 

1715 

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

1717 raise RuntimeError( 

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

1719 ) 

1720 

1721 with request_context(request=request): 

1722 response = await transport.handle_async_request(request) 

1723 

1724 assert isinstance(response.stream, AsyncByteStream) 

1725 response.request = request 

1726 response.stream = BoundAsyncStream( 

1727 response.stream, response=response, timer=timer 

1728 ) 

1729 self.cookies.extract_cookies(response) 

1730 response.default_encoding = self._default_encoding 

1731 

1732 status = f"{response.status_code} {response.reason_phrase}" 

1733 response_line = f"{response.http_version} {status}" 

1734 logger.debug( 

1735 'HTTP Request: %s %s "%s"', request.method, request.url, response_line 

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)