_client.py 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037
  1. import datetime
  2. import enum
  3. import logging
  4. import typing
  5. import warnings
  6. from contextlib import asynccontextmanager, contextmanager
  7. from types import TracebackType
  8. from .__version__ import __version__
  9. from ._auth import Auth, BasicAuth, FunctionAuth
  10. from ._config import (
  11. DEFAULT_LIMITS,
  12. DEFAULT_MAX_REDIRECTS,
  13. DEFAULT_TIMEOUT_CONFIG,
  14. Limits,
  15. Proxy,
  16. Timeout,
  17. )
  18. from ._decoders import SUPPORTED_DECODERS
  19. from ._exceptions import (
  20. InvalidURL,
  21. RemoteProtocolError,
  22. TooManyRedirects,
  23. request_context,
  24. )
  25. from ._models import Cookies, Headers, Request, Response
  26. from ._status_codes import codes
  27. from ._transports.asgi import ASGITransport
  28. from ._transports.base import AsyncBaseTransport, BaseTransport
  29. from ._transports.default import AsyncHTTPTransport, HTTPTransport
  30. from ._transports.wsgi import WSGITransport
  31. from ._types import (
  32. AsyncByteStream,
  33. AuthTypes,
  34. CertTypes,
  35. CookieTypes,
  36. HeaderTypes,
  37. ProxiesTypes,
  38. ProxyTypes,
  39. QueryParamTypes,
  40. RequestContent,
  41. RequestData,
  42. RequestExtensions,
  43. RequestFiles,
  44. SyncByteStream,
  45. TimeoutTypes,
  46. URLTypes,
  47. VerifyTypes,
  48. )
  49. from ._urls import URL, QueryParams
  50. from ._utils import (
  51. Timer,
  52. URLPattern,
  53. get_environment_proxies,
  54. is_https_redirect,
  55. same_origin,
  56. )
  57. # The type annotation for @classmethod and context managers here follows PEP 484
  58. # https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods
  59. T = typing.TypeVar("T", bound="Client")
  60. U = typing.TypeVar("U", bound="AsyncClient")
  61. class UseClientDefault:
  62. """
  63. For some parameters such as `auth=...` and `timeout=...` we need to be able
  64. to indicate the default "unset" state, in a way that is distinctly different
  65. to using `None`.
  66. The default "unset" state indicates that whatever default is set on the
  67. client should be used. This is different to setting `None`, which
  68. explicitly disables the parameter, possibly overriding a client default.
  69. For example we use `timeout=USE_CLIENT_DEFAULT` in the `request()` signature.
  70. Omitting the `timeout` parameter will send a request using whatever default
  71. timeout has been configured on the client. Including `timeout=None` will
  72. ensure no timeout is used.
  73. Note that user code shouldn't need to use the `USE_CLIENT_DEFAULT` constant,
  74. but it is used internally when a parameter is not included.
  75. """
  76. USE_CLIENT_DEFAULT = UseClientDefault()
  77. logger = logging.getLogger("httpx")
  78. USER_AGENT = f"python-httpx/{__version__}"
  79. ACCEPT_ENCODING = ", ".join(
  80. [key for key in SUPPORTED_DECODERS.keys() if key != "identity"]
  81. )
  82. class ClientState(enum.Enum):
  83. # UNOPENED:
  84. # The client has been instantiated, but has not been used to send a request,
  85. # or been opened by entering the context of a `with` block.
  86. UNOPENED = 1
  87. # OPENED:
  88. # The client has either sent a request, or is within a `with` block.
  89. OPENED = 2
  90. # CLOSED:
  91. # The client has either exited the `with` block, or `close()` has
  92. # been called explicitly.
  93. CLOSED = 3
  94. class BoundSyncStream(SyncByteStream):
  95. """
  96. A byte stream that is bound to a given response instance, and that
  97. ensures the `response.elapsed` is set once the response is closed.
  98. """
  99. def __init__(
  100. self, stream: SyncByteStream, response: Response, timer: Timer
  101. ) -> None:
  102. self._stream = stream
  103. self._response = response
  104. self._timer = timer
  105. def __iter__(self) -> typing.Iterator[bytes]:
  106. for chunk in self._stream:
  107. yield chunk
  108. def close(self) -> None:
  109. seconds = self._timer.sync_elapsed()
  110. self._response.elapsed = datetime.timedelta(seconds=seconds)
  111. self._stream.close()
  112. class BoundAsyncStream(AsyncByteStream):
  113. """
  114. An async byte stream that is bound to a given response instance, and that
  115. ensures the `response.elapsed` is set once the response is closed.
  116. """
  117. def __init__(
  118. self, stream: AsyncByteStream, response: Response, timer: Timer
  119. ) -> None:
  120. self._stream = stream
  121. self._response = response
  122. self._timer = timer
  123. async def __aiter__(self) -> typing.AsyncIterator[bytes]:
  124. async for chunk in self._stream:
  125. yield chunk
  126. async def aclose(self) -> None:
  127. seconds = await self._timer.async_elapsed()
  128. self._response.elapsed = datetime.timedelta(seconds=seconds)
  129. await self._stream.aclose()
  130. EventHook = typing.Callable[..., typing.Any]
  131. class BaseClient:
  132. def __init__(
  133. self,
  134. *,
  135. auth: typing.Optional[AuthTypes] = None,
  136. params: typing.Optional[QueryParamTypes] = None,
  137. headers: typing.Optional[HeaderTypes] = None,
  138. cookies: typing.Optional[CookieTypes] = None,
  139. timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
  140. follow_redirects: bool = False,
  141. max_redirects: int = DEFAULT_MAX_REDIRECTS,
  142. event_hooks: typing.Optional[
  143. typing.Mapping[str, typing.List[EventHook]]
  144. ] = None,
  145. base_url: URLTypes = "",
  146. trust_env: bool = True,
  147. default_encoding: typing.Union[str, typing.Callable[[bytes], str]] = "utf-8",
  148. ) -> None:
  149. event_hooks = {} if event_hooks is None else event_hooks
  150. self._base_url = self._enforce_trailing_slash(URL(base_url))
  151. self._auth = self._build_auth(auth)
  152. self._params = QueryParams(params)
  153. self.headers = Headers(headers)
  154. self._cookies = Cookies(cookies)
  155. self._timeout = Timeout(timeout)
  156. self.follow_redirects = follow_redirects
  157. self.max_redirects = max_redirects
  158. self._event_hooks = {
  159. "request": list(event_hooks.get("request", [])),
  160. "response": list(event_hooks.get("response", [])),
  161. }
  162. self._trust_env = trust_env
  163. self._default_encoding = default_encoding
  164. self._state = ClientState.UNOPENED
  165. @property
  166. def is_closed(self) -> bool:
  167. """
  168. Check if the client being closed
  169. """
  170. return self._state == ClientState.CLOSED
  171. @property
  172. def trust_env(self) -> bool:
  173. return self._trust_env
  174. def _enforce_trailing_slash(self, url: URL) -> URL:
  175. if url.raw_path.endswith(b"/"):
  176. return url
  177. return url.copy_with(raw_path=url.raw_path + b"/")
  178. def _get_proxy_map(
  179. self, proxies: typing.Optional[ProxiesTypes], allow_env_proxies: bool
  180. ) -> typing.Dict[str, typing.Optional[Proxy]]:
  181. if proxies is None:
  182. if allow_env_proxies:
  183. return {
  184. key: None if url is None else Proxy(url=url)
  185. for key, url in get_environment_proxies().items()
  186. }
  187. return {}
  188. if isinstance(proxies, dict):
  189. new_proxies = {}
  190. for key, value in proxies.items():
  191. proxy = Proxy(url=value) if isinstance(value, (str, URL)) else value
  192. new_proxies[str(key)] = proxy
  193. return new_proxies
  194. else:
  195. proxy = Proxy(url=proxies) if isinstance(proxies, (str, URL)) else proxies
  196. return {"all://": proxy}
  197. @property
  198. def timeout(self) -> Timeout:
  199. return self._timeout
  200. @timeout.setter
  201. def timeout(self, timeout: TimeoutTypes) -> None:
  202. self._timeout = Timeout(timeout)
  203. @property
  204. def event_hooks(self) -> typing.Dict[str, typing.List[EventHook]]:
  205. return self._event_hooks
  206. @event_hooks.setter
  207. def event_hooks(
  208. self, event_hooks: typing.Dict[str, typing.List[EventHook]]
  209. ) -> None:
  210. self._event_hooks = {
  211. "request": list(event_hooks.get("request", [])),
  212. "response": list(event_hooks.get("response", [])),
  213. }
  214. @property
  215. def auth(self) -> typing.Optional[Auth]:
  216. """
  217. Authentication class used when none is passed at the request-level.
  218. See also [Authentication][0].
  219. [0]: /quickstart/#authentication
  220. """
  221. return self._auth
  222. @auth.setter
  223. def auth(self, auth: AuthTypes) -> None:
  224. self._auth = self._build_auth(auth)
  225. @property
  226. def base_url(self) -> URL:
  227. """
  228. Base URL to use when sending requests with relative URLs.
  229. """
  230. return self._base_url
  231. @base_url.setter
  232. def base_url(self, url: URLTypes) -> None:
  233. self._base_url = self._enforce_trailing_slash(URL(url))
  234. @property
  235. def headers(self) -> Headers:
  236. """
  237. HTTP headers to include when sending requests.
  238. """
  239. return self._headers
  240. @headers.setter
  241. def headers(self, headers: HeaderTypes) -> None:
  242. client_headers = Headers(
  243. {
  244. b"Accept": b"*/*",
  245. b"Accept-Encoding": ACCEPT_ENCODING.encode("ascii"),
  246. b"Connection": b"keep-alive",
  247. b"User-Agent": USER_AGENT.encode("ascii"),
  248. }
  249. )
  250. client_headers.update(headers)
  251. self._headers = client_headers
  252. @property
  253. def cookies(self) -> Cookies:
  254. """
  255. Cookie values to include when sending requests.
  256. """
  257. return self._cookies
  258. @cookies.setter
  259. def cookies(self, cookies: CookieTypes) -> None:
  260. self._cookies = Cookies(cookies)
  261. @property
  262. def params(self) -> QueryParams:
  263. """
  264. Query parameters to include in the URL when sending requests.
  265. """
  266. return self._params
  267. @params.setter
  268. def params(self, params: QueryParamTypes) -> None:
  269. self._params = QueryParams(params)
  270. def build_request(
  271. self,
  272. method: str,
  273. url: URLTypes,
  274. *,
  275. content: typing.Optional[RequestContent] = None,
  276. data: typing.Optional[RequestData] = None,
  277. files: typing.Optional[RequestFiles] = None,
  278. json: typing.Optional[typing.Any] = None,
  279. params: typing.Optional[QueryParamTypes] = None,
  280. headers: typing.Optional[HeaderTypes] = None,
  281. cookies: typing.Optional[CookieTypes] = None,
  282. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  283. extensions: typing.Optional[RequestExtensions] = None,
  284. ) -> Request:
  285. """
  286. Build and return a request instance.
  287. * The `params`, `headers` and `cookies` arguments
  288. are merged with any values set on the client.
  289. * The `url` argument is merged with any `base_url` set on the client.
  290. See also: [Request instances][0]
  291. [0]: /advanced/#request-instances
  292. """
  293. url = self._merge_url(url)
  294. headers = self._merge_headers(headers)
  295. cookies = self._merge_cookies(cookies)
  296. params = self._merge_queryparams(params)
  297. extensions = {} if extensions is None else extensions
  298. if "timeout" not in extensions:
  299. timeout = (
  300. self.timeout
  301. if isinstance(timeout, UseClientDefault)
  302. else Timeout(timeout)
  303. )
  304. extensions = dict(**extensions, timeout=timeout.as_dict())
  305. return Request(
  306. method,
  307. url,
  308. content=content,
  309. data=data,
  310. files=files,
  311. json=json,
  312. params=params,
  313. headers=headers,
  314. cookies=cookies,
  315. extensions=extensions,
  316. )
  317. def _merge_url(self, url: URLTypes) -> URL:
  318. """
  319. Merge a URL argument together with any 'base_url' on the client,
  320. to create the URL used for the outgoing request.
  321. """
  322. merge_url = URL(url)
  323. if merge_url.is_relative_url:
  324. # To merge URLs we always append to the base URL. To get this
  325. # behaviour correct we always ensure the base URL ends in a '/'
  326. # separator, and strip any leading '/' from the merge URL.
  327. #
  328. # So, eg...
  329. #
  330. # >>> client = Client(base_url="https://www.example.com/subpath")
  331. # >>> client.base_url
  332. # URL('https://www.example.com/subpath/')
  333. # >>> client.build_request("GET", "/path").url
  334. # URL('https://www.example.com/subpath/path')
  335. merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
  336. return self.base_url.copy_with(raw_path=merge_raw_path)
  337. return merge_url
  338. def _merge_cookies(
  339. self, cookies: typing.Optional[CookieTypes] = None
  340. ) -> typing.Optional[CookieTypes]:
  341. """
  342. Merge a cookies argument together with any cookies on the client,
  343. to create the cookies used for the outgoing request.
  344. """
  345. if cookies or self.cookies:
  346. merged_cookies = Cookies(self.cookies)
  347. merged_cookies.update(cookies)
  348. return merged_cookies
  349. return cookies
  350. def _merge_headers(
  351. self, headers: typing.Optional[HeaderTypes] = None
  352. ) -> typing.Optional[HeaderTypes]:
  353. """
  354. Merge a headers argument together with any headers on the client,
  355. to create the headers used for the outgoing request.
  356. """
  357. merged_headers = Headers(self.headers)
  358. merged_headers.update(headers)
  359. return merged_headers
  360. def _merge_queryparams(
  361. self, params: typing.Optional[QueryParamTypes] = None
  362. ) -> typing.Optional[QueryParamTypes]:
  363. """
  364. Merge a queryparams argument together with any queryparams on the client,
  365. to create the queryparams used for the outgoing request.
  366. """
  367. if params or self.params:
  368. merged_queryparams = QueryParams(self.params)
  369. return merged_queryparams.merge(params)
  370. return params
  371. def _build_auth(self, auth: typing.Optional[AuthTypes]) -> typing.Optional[Auth]:
  372. if auth is None:
  373. return None
  374. elif isinstance(auth, tuple):
  375. return BasicAuth(username=auth[0], password=auth[1])
  376. elif isinstance(auth, Auth):
  377. return auth
  378. elif callable(auth):
  379. return FunctionAuth(func=auth)
  380. else:
  381. raise TypeError(f'Invalid "auth" argument: {auth!r}')
  382. def _build_request_auth(
  383. self,
  384. request: Request,
  385. auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT,
  386. ) -> Auth:
  387. auth = (
  388. self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth)
  389. )
  390. if auth is not None:
  391. return auth
  392. username, password = request.url.username, request.url.password
  393. if username or password:
  394. return BasicAuth(username=username, password=password)
  395. return Auth()
  396. def _build_redirect_request(self, request: Request, response: Response) -> Request:
  397. """
  398. Given a request and a redirect response, return a new request that
  399. should be used to effect the redirect.
  400. """
  401. method = self._redirect_method(request, response)
  402. url = self._redirect_url(request, response)
  403. headers = self._redirect_headers(request, url, method)
  404. stream = self._redirect_stream(request, method)
  405. cookies = Cookies(self.cookies)
  406. return Request(
  407. method=method,
  408. url=url,
  409. headers=headers,
  410. cookies=cookies,
  411. stream=stream,
  412. extensions=request.extensions,
  413. )
  414. def _redirect_method(self, request: Request, response: Response) -> str:
  415. """
  416. When being redirected we may want to change the method of the request
  417. based on certain specs or browser behavior.
  418. """
  419. method = request.method
  420. # https://tools.ietf.org/html/rfc7231#section-6.4.4
  421. if response.status_code == codes.SEE_OTHER and method != "HEAD":
  422. method = "GET"
  423. # Do what the browsers do, despite standards...
  424. # Turn 302s into GETs.
  425. if response.status_code == codes.FOUND and method != "HEAD":
  426. method = "GET"
  427. # If a POST is responded to with a 301, turn it into a GET.
  428. # This bizarre behaviour is explained in 'requests' issue 1704.
  429. if response.status_code == codes.MOVED_PERMANENTLY and method == "POST":
  430. method = "GET"
  431. return method
  432. def _redirect_url(self, request: Request, response: Response) -> URL:
  433. """
  434. Return the URL for the redirect to follow.
  435. """
  436. location = response.headers["Location"]
  437. try:
  438. url = URL(location)
  439. except InvalidURL as exc:
  440. raise RemoteProtocolError(
  441. f"Invalid URL in location header: {exc}.", request=request
  442. ) from None
  443. # Handle malformed 'Location' headers that are "absolute" form, have no host.
  444. # See: https://github.com/encode/httpx/issues/771
  445. if url.scheme and not url.host:
  446. url = url.copy_with(host=request.url.host)
  447. # Facilitate relative 'Location' headers, as allowed by RFC 7231.
  448. # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
  449. if url.is_relative_url:
  450. url = request.url.join(url)
  451. # Attach previous fragment if needed (RFC 7231 7.1.2)
  452. if request.url.fragment and not url.fragment:
  453. url = url.copy_with(fragment=request.url.fragment)
  454. return url
  455. def _redirect_headers(self, request: Request, url: URL, method: str) -> Headers:
  456. """
  457. Return the headers that should be used for the redirect request.
  458. """
  459. headers = Headers(request.headers)
  460. if not same_origin(url, request.url):
  461. if not is_https_redirect(request.url, url):
  462. # Strip Authorization headers when responses are redirected
  463. # away from the origin. (Except for direct HTTP to HTTPS redirects.)
  464. headers.pop("Authorization", None)
  465. # Update the Host header.
  466. headers["Host"] = url.netloc.decode("ascii")
  467. if method != request.method and method == "GET":
  468. # If we've switch to a 'GET' request, then strip any headers which
  469. # are only relevant to the request body.
  470. headers.pop("Content-Length", None)
  471. headers.pop("Transfer-Encoding", None)
  472. # We should use the client cookie store to determine any cookie header,
  473. # rather than whatever was on the original outgoing request.
  474. headers.pop("Cookie", None)
  475. return headers
  476. def _redirect_stream(
  477. self, request: Request, method: str
  478. ) -> typing.Optional[typing.Union[SyncByteStream, AsyncByteStream]]:
  479. """
  480. Return the body that should be used for the redirect request.
  481. """
  482. if method != request.method and method == "GET":
  483. return None
  484. return request.stream
  485. class Client(BaseClient):
  486. """
  487. An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc.
  488. It can be shared between threads.
  489. Usage:
  490. ```python
  491. >>> client = httpx.Client()
  492. >>> response = client.get('https://example.org')
  493. ```
  494. **Parameters:**
  495. * **auth** - *(optional)* An authentication class to use when sending
  496. requests.
  497. * **params** - *(optional)* Query parameters to include in request URLs, as
  498. a string, dictionary, or sequence of two-tuples.
  499. * **headers** - *(optional)* Dictionary of HTTP headers to include when
  500. sending requests.
  501. * **cookies** - *(optional)* Dictionary of Cookie items to include when
  502. sending requests.
  503. * **verify** - *(optional)* SSL certificates (a.k.a CA bundle) used to
  504. verify the identity of requested hosts. Either `True` (default CA bundle),
  505. a path to an SSL certificate file, an `ssl.SSLContext`, or `False`
  506. (which will disable verification).
  507. * **cert** - *(optional)* An SSL certificate used by the requested host
  508. to authenticate the client. Either a path to an SSL certificate file, or
  509. two-tuple of (certificate file, key file), or a three-tuple of (certificate
  510. file, key file, password).
  511. * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
  512. * **proxies** - *(optional)* A dictionary mapping proxy keys to proxy
  513. URLs.
  514. * **timeout** - *(optional)* The timeout configuration to use when sending
  515. requests.
  516. * **limits** - *(optional)* The limits configuration to use.
  517. * **max_redirects** - *(optional)* The maximum number of redirect responses
  518. that should be followed.
  519. * **base_url** - *(optional)* A URL to use as the base when building
  520. request URLs.
  521. * **transport** - *(optional)* A transport class to use for sending requests
  522. over the network.
  523. * **app** - *(optional)* An WSGI application to send requests to,
  524. rather than sending actual network requests.
  525. * **trust_env** - *(optional)* Enables or disables usage of environment
  526. variables for configuration.
  527. * **default_encoding** - *(optional)* The default encoding to use for decoding
  528. response text, if no charset information is included in a response Content-Type
  529. header. Set to a callable for automatic character set detection. Default: "utf-8".
  530. """
  531. def __init__(
  532. self,
  533. *,
  534. auth: typing.Optional[AuthTypes] = None,
  535. params: typing.Optional[QueryParamTypes] = None,
  536. headers: typing.Optional[HeaderTypes] = None,
  537. cookies: typing.Optional[CookieTypes] = None,
  538. verify: VerifyTypes = True,
  539. cert: typing.Optional[CertTypes] = None,
  540. http1: bool = True,
  541. http2: bool = False,
  542. proxy: typing.Optional[ProxyTypes] = None,
  543. proxies: typing.Optional[ProxiesTypes] = None,
  544. mounts: typing.Optional[
  545. typing.Mapping[str, typing.Optional[BaseTransport]]
  546. ] = None,
  547. timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
  548. follow_redirects: bool = False,
  549. limits: Limits = DEFAULT_LIMITS,
  550. max_redirects: int = DEFAULT_MAX_REDIRECTS,
  551. event_hooks: typing.Optional[
  552. typing.Mapping[str, typing.List[EventHook]]
  553. ] = None,
  554. base_url: URLTypes = "",
  555. transport: typing.Optional[BaseTransport] = None,
  556. app: typing.Optional[typing.Callable[..., typing.Any]] = None,
  557. trust_env: bool = True,
  558. default_encoding: typing.Union[str, typing.Callable[[bytes], str]] = "utf-8",
  559. ) -> None:
  560. super().__init__(
  561. auth=auth,
  562. params=params,
  563. headers=headers,
  564. cookies=cookies,
  565. timeout=timeout,
  566. follow_redirects=follow_redirects,
  567. max_redirects=max_redirects,
  568. event_hooks=event_hooks,
  569. base_url=base_url,
  570. trust_env=trust_env,
  571. default_encoding=default_encoding,
  572. )
  573. if http2:
  574. try:
  575. import h2 # noqa
  576. except ImportError: # pragma: no cover
  577. raise ImportError(
  578. "Using http2=True, but the 'h2' package is not installed. "
  579. "Make sure to install httpx using `pip install httpx[http2]`."
  580. ) from None
  581. if proxies:
  582. message = (
  583. "The 'proxies' argument is now deprecated."
  584. " Use 'proxy' or 'mounts' instead."
  585. )
  586. warnings.warn(message, DeprecationWarning)
  587. if proxy:
  588. raise RuntimeError("Use either `proxy` or 'proxies', not both.")
  589. allow_env_proxies = trust_env and app is None and transport is None
  590. proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies)
  591. self._transport = self._init_transport(
  592. verify=verify,
  593. cert=cert,
  594. http1=http1,
  595. http2=http2,
  596. limits=limits,
  597. transport=transport,
  598. app=app,
  599. trust_env=trust_env,
  600. )
  601. self._mounts: typing.Dict[URLPattern, typing.Optional[BaseTransport]] = {
  602. URLPattern(key): None
  603. if proxy is None
  604. else self._init_proxy_transport(
  605. proxy,
  606. verify=verify,
  607. cert=cert,
  608. http1=http1,
  609. http2=http2,
  610. limits=limits,
  611. trust_env=trust_env,
  612. )
  613. for key, proxy in proxy_map.items()
  614. }
  615. if mounts is not None:
  616. self._mounts.update(
  617. {URLPattern(key): transport for key, transport in mounts.items()}
  618. )
  619. self._mounts = dict(sorted(self._mounts.items()))
  620. def _init_transport(
  621. self,
  622. verify: VerifyTypes = True,
  623. cert: typing.Optional[CertTypes] = None,
  624. http1: bool = True,
  625. http2: bool = False,
  626. limits: Limits = DEFAULT_LIMITS,
  627. transport: typing.Optional[BaseTransport] = None,
  628. app: typing.Optional[typing.Callable[..., typing.Any]] = None,
  629. trust_env: bool = True,
  630. ) -> BaseTransport:
  631. if transport is not None:
  632. return transport
  633. if app is not None:
  634. return WSGITransport(app=app)
  635. return HTTPTransport(
  636. verify=verify,
  637. cert=cert,
  638. http1=http1,
  639. http2=http2,
  640. limits=limits,
  641. trust_env=trust_env,
  642. )
  643. def _init_proxy_transport(
  644. self,
  645. proxy: Proxy,
  646. verify: VerifyTypes = True,
  647. cert: typing.Optional[CertTypes] = None,
  648. http1: bool = True,
  649. http2: bool = False,
  650. limits: Limits = DEFAULT_LIMITS,
  651. trust_env: bool = True,
  652. ) -> BaseTransport:
  653. return HTTPTransport(
  654. verify=verify,
  655. cert=cert,
  656. http1=http1,
  657. http2=http2,
  658. limits=limits,
  659. trust_env=trust_env,
  660. proxy=proxy,
  661. )
  662. def _transport_for_url(self, url: URL) -> BaseTransport:
  663. """
  664. Returns the transport instance that should be used for a given URL.
  665. This will either be the standard connection pool, or a proxy.
  666. """
  667. for pattern, transport in self._mounts.items():
  668. if pattern.matches(url):
  669. return self._transport if transport is None else transport
  670. return self._transport
  671. def request(
  672. self,
  673. method: str,
  674. url: URLTypes,
  675. *,
  676. content: typing.Optional[RequestContent] = None,
  677. data: typing.Optional[RequestData] = None,
  678. files: typing.Optional[RequestFiles] = None,
  679. json: typing.Optional[typing.Any] = None,
  680. params: typing.Optional[QueryParamTypes] = None,
  681. headers: typing.Optional[HeaderTypes] = None,
  682. cookies: typing.Optional[CookieTypes] = None,
  683. auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT,
  684. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  685. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  686. extensions: typing.Optional[RequestExtensions] = None,
  687. ) -> Response:
  688. """
  689. Build and send a request.
  690. Equivalent to:
  691. ```python
  692. request = client.build_request(...)
  693. response = client.send(request, ...)
  694. ```
  695. See `Client.build_request()`, `Client.send()` and
  696. [Merging of configuration][0] for how the various parameters
  697. are merged with client-level configuration.
  698. [0]: /advanced/#merging-of-configuration
  699. """
  700. if cookies is not None:
  701. message = (
  702. "Setting per-request cookies=<...> is being deprecated, because "
  703. "the expected behaviour on cookie persistence is ambiguous. Set "
  704. "cookies directly on the client instance instead."
  705. )
  706. warnings.warn(message, DeprecationWarning)
  707. request = self.build_request(
  708. method=method,
  709. url=url,
  710. content=content,
  711. data=data,
  712. files=files,
  713. json=json,
  714. params=params,
  715. headers=headers,
  716. cookies=cookies,
  717. timeout=timeout,
  718. extensions=extensions,
  719. )
  720. return self.send(request, auth=auth, follow_redirects=follow_redirects)
  721. @contextmanager
  722. def stream(
  723. self,
  724. method: str,
  725. url: URLTypes,
  726. *,
  727. content: typing.Optional[RequestContent] = None,
  728. data: typing.Optional[RequestData] = None,
  729. files: typing.Optional[RequestFiles] = None,
  730. json: typing.Optional[typing.Any] = None,
  731. params: typing.Optional[QueryParamTypes] = None,
  732. headers: typing.Optional[HeaderTypes] = None,
  733. cookies: typing.Optional[CookieTypes] = None,
  734. auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT,
  735. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  736. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  737. extensions: typing.Optional[RequestExtensions] = None,
  738. ) -> typing.Iterator[Response]:
  739. """
  740. Alternative to `httpx.request()` that streams the response body
  741. instead of loading it into memory at once.
  742. **Parameters**: See `httpx.request`.
  743. See also: [Streaming Responses][0]
  744. [0]: /quickstart#streaming-responses
  745. """
  746. request = self.build_request(
  747. method=method,
  748. url=url,
  749. content=content,
  750. data=data,
  751. files=files,
  752. json=json,
  753. params=params,
  754. headers=headers,
  755. cookies=cookies,
  756. timeout=timeout,
  757. extensions=extensions,
  758. )
  759. response = self.send(
  760. request=request,
  761. auth=auth,
  762. follow_redirects=follow_redirects,
  763. stream=True,
  764. )
  765. try:
  766. yield response
  767. finally:
  768. response.close()
  769. def send(
  770. self,
  771. request: Request,
  772. *,
  773. stream: bool = False,
  774. auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT,
  775. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  776. ) -> Response:
  777. """
  778. Send a request.
  779. The request is sent as-is, unmodified.
  780. Typically you'll want to build one with `Client.build_request()`
  781. so that any client-level configuration is merged into the request,
  782. but passing an explicit `httpx.Request()` is supported as well.
  783. See also: [Request instances][0]
  784. [0]: /advanced/#request-instances
  785. """
  786. if self._state == ClientState.CLOSED:
  787. raise RuntimeError("Cannot send a request, as the client has been closed.")
  788. self._state = ClientState.OPENED
  789. follow_redirects = (
  790. self.follow_redirects
  791. if isinstance(follow_redirects, UseClientDefault)
  792. else follow_redirects
  793. )
  794. auth = self._build_request_auth(request, auth)
  795. response = self._send_handling_auth(
  796. request,
  797. auth=auth,
  798. follow_redirects=follow_redirects,
  799. history=[],
  800. )
  801. try:
  802. if not stream:
  803. response.read()
  804. return response
  805. except BaseException as exc:
  806. response.close()
  807. raise exc
  808. def _send_handling_auth(
  809. self,
  810. request: Request,
  811. auth: Auth,
  812. follow_redirects: bool,
  813. history: typing.List[Response],
  814. ) -> Response:
  815. auth_flow = auth.sync_auth_flow(request)
  816. try:
  817. request = next(auth_flow)
  818. while True:
  819. response = self._send_handling_redirects(
  820. request,
  821. follow_redirects=follow_redirects,
  822. history=history,
  823. )
  824. try:
  825. try:
  826. next_request = auth_flow.send(response)
  827. except StopIteration:
  828. return response
  829. response.history = list(history)
  830. response.read()
  831. request = next_request
  832. history.append(response)
  833. except BaseException as exc:
  834. response.close()
  835. raise exc
  836. finally:
  837. auth_flow.close()
  838. def _send_handling_redirects(
  839. self,
  840. request: Request,
  841. follow_redirects: bool,
  842. history: typing.List[Response],
  843. ) -> Response:
  844. while True:
  845. if len(history) > self.max_redirects:
  846. raise TooManyRedirects(
  847. "Exceeded maximum allowed redirects.", request=request
  848. )
  849. for hook in self._event_hooks["request"]:
  850. hook(request)
  851. response = self._send_single_request(request)
  852. try:
  853. for hook in self._event_hooks["response"]:
  854. hook(response)
  855. response.history = list(history)
  856. if not response.has_redirect_location:
  857. return response
  858. request = self._build_redirect_request(request, response)
  859. history = history + [response]
  860. if follow_redirects:
  861. response.read()
  862. else:
  863. response.next_request = request
  864. return response
  865. except BaseException as exc:
  866. response.close()
  867. raise exc
  868. def _send_single_request(self, request: Request) -> Response:
  869. """
  870. Sends a single request, without handling any redirections.
  871. """
  872. transport = self._transport_for_url(request.url)
  873. timer = Timer()
  874. timer.sync_start()
  875. if not isinstance(request.stream, SyncByteStream):
  876. raise RuntimeError(
  877. "Attempted to send an async request with a sync Client instance."
  878. )
  879. with request_context(request=request):
  880. response = transport.handle_request(request)
  881. assert isinstance(response.stream, SyncByteStream)
  882. response.request = request
  883. response.stream = BoundSyncStream(
  884. response.stream, response=response, timer=timer
  885. )
  886. self.cookies.extract_cookies(response)
  887. response.default_encoding = self._default_encoding
  888. logger.info(
  889. 'HTTP Request: %s %s "%s %d %s"',
  890. request.method,
  891. request.url,
  892. response.http_version,
  893. response.status_code,
  894. response.reason_phrase,
  895. )
  896. return response
  897. def get(
  898. self,
  899. url: URLTypes,
  900. *,
  901. params: typing.Optional[QueryParamTypes] = None,
  902. headers: typing.Optional[HeaderTypes] = None,
  903. cookies: typing.Optional[CookieTypes] = None,
  904. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  905. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  906. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  907. extensions: typing.Optional[RequestExtensions] = None,
  908. ) -> Response:
  909. """
  910. Send a `GET` request.
  911. **Parameters**: See `httpx.request`.
  912. """
  913. return self.request(
  914. "GET",
  915. url,
  916. params=params,
  917. headers=headers,
  918. cookies=cookies,
  919. auth=auth,
  920. follow_redirects=follow_redirects,
  921. timeout=timeout,
  922. extensions=extensions,
  923. )
  924. def options(
  925. self,
  926. url: URLTypes,
  927. *,
  928. params: typing.Optional[QueryParamTypes] = None,
  929. headers: typing.Optional[HeaderTypes] = None,
  930. cookies: typing.Optional[CookieTypes] = None,
  931. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  932. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  933. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  934. extensions: typing.Optional[RequestExtensions] = None,
  935. ) -> Response:
  936. """
  937. Send an `OPTIONS` request.
  938. **Parameters**: See `httpx.request`.
  939. """
  940. return self.request(
  941. "OPTIONS",
  942. url,
  943. params=params,
  944. headers=headers,
  945. cookies=cookies,
  946. auth=auth,
  947. follow_redirects=follow_redirects,
  948. timeout=timeout,
  949. extensions=extensions,
  950. )
  951. def head(
  952. self,
  953. url: URLTypes,
  954. *,
  955. params: typing.Optional[QueryParamTypes] = None,
  956. headers: typing.Optional[HeaderTypes] = None,
  957. cookies: typing.Optional[CookieTypes] = None,
  958. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  959. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  960. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  961. extensions: typing.Optional[RequestExtensions] = None,
  962. ) -> Response:
  963. """
  964. Send a `HEAD` request.
  965. **Parameters**: See `httpx.request`.
  966. """
  967. return self.request(
  968. "HEAD",
  969. url,
  970. params=params,
  971. headers=headers,
  972. cookies=cookies,
  973. auth=auth,
  974. follow_redirects=follow_redirects,
  975. timeout=timeout,
  976. extensions=extensions,
  977. )
  978. def post(
  979. self,
  980. url: URLTypes,
  981. *,
  982. content: typing.Optional[RequestContent] = None,
  983. data: typing.Optional[RequestData] = None,
  984. files: typing.Optional[RequestFiles] = None,
  985. json: typing.Optional[typing.Any] = None,
  986. params: typing.Optional[QueryParamTypes] = None,
  987. headers: typing.Optional[HeaderTypes] = None,
  988. cookies: typing.Optional[CookieTypes] = None,
  989. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  990. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  991. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  992. extensions: typing.Optional[RequestExtensions] = None,
  993. ) -> Response:
  994. """
  995. Send a `POST` request.
  996. **Parameters**: See `httpx.request`.
  997. """
  998. return self.request(
  999. "POST",
  1000. url,
  1001. content=content,
  1002. data=data,
  1003. files=files,
  1004. json=json,
  1005. params=params,
  1006. headers=headers,
  1007. cookies=cookies,
  1008. auth=auth,
  1009. follow_redirects=follow_redirects,
  1010. timeout=timeout,
  1011. extensions=extensions,
  1012. )
  1013. def put(
  1014. self,
  1015. url: URLTypes,
  1016. *,
  1017. content: typing.Optional[RequestContent] = None,
  1018. data: typing.Optional[RequestData] = None,
  1019. files: typing.Optional[RequestFiles] = None,
  1020. json: typing.Optional[typing.Any] = None,
  1021. params: typing.Optional[QueryParamTypes] = None,
  1022. headers: typing.Optional[HeaderTypes] = None,
  1023. cookies: typing.Optional[CookieTypes] = None,
  1024. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1025. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1026. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1027. extensions: typing.Optional[RequestExtensions] = None,
  1028. ) -> Response:
  1029. """
  1030. Send a `PUT` request.
  1031. **Parameters**: See `httpx.request`.
  1032. """
  1033. return self.request(
  1034. "PUT",
  1035. url,
  1036. content=content,
  1037. data=data,
  1038. files=files,
  1039. json=json,
  1040. params=params,
  1041. headers=headers,
  1042. cookies=cookies,
  1043. auth=auth,
  1044. follow_redirects=follow_redirects,
  1045. timeout=timeout,
  1046. extensions=extensions,
  1047. )
  1048. def patch(
  1049. self,
  1050. url: URLTypes,
  1051. *,
  1052. content: typing.Optional[RequestContent] = None,
  1053. data: typing.Optional[RequestData] = None,
  1054. files: typing.Optional[RequestFiles] = None,
  1055. json: typing.Optional[typing.Any] = None,
  1056. params: typing.Optional[QueryParamTypes] = None,
  1057. headers: typing.Optional[HeaderTypes] = None,
  1058. cookies: typing.Optional[CookieTypes] = None,
  1059. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1060. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1061. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1062. extensions: typing.Optional[RequestExtensions] = None,
  1063. ) -> Response:
  1064. """
  1065. Send a `PATCH` request.
  1066. **Parameters**: See `httpx.request`.
  1067. """
  1068. return self.request(
  1069. "PATCH",
  1070. url,
  1071. content=content,
  1072. data=data,
  1073. files=files,
  1074. json=json,
  1075. params=params,
  1076. headers=headers,
  1077. cookies=cookies,
  1078. auth=auth,
  1079. follow_redirects=follow_redirects,
  1080. timeout=timeout,
  1081. extensions=extensions,
  1082. )
  1083. def delete(
  1084. self,
  1085. url: URLTypes,
  1086. *,
  1087. params: typing.Optional[QueryParamTypes] = None,
  1088. headers: typing.Optional[HeaderTypes] = None,
  1089. cookies: typing.Optional[CookieTypes] = None,
  1090. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1091. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1092. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1093. extensions: typing.Optional[RequestExtensions] = None,
  1094. ) -> Response:
  1095. """
  1096. Send a `DELETE` request.
  1097. **Parameters**: See `httpx.request`.
  1098. """
  1099. return self.request(
  1100. "DELETE",
  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. def close(self) -> None:
  1111. """
  1112. Close transport and proxies.
  1113. """
  1114. if self._state != ClientState.CLOSED:
  1115. self._state = ClientState.CLOSED
  1116. self._transport.close()
  1117. for transport in self._mounts.values():
  1118. if transport is not None:
  1119. transport.close()
  1120. def __enter__(self: T) -> T:
  1121. if self._state != ClientState.UNOPENED:
  1122. msg = {
  1123. ClientState.OPENED: "Cannot open a client instance more than once.",
  1124. ClientState.CLOSED: (
  1125. "Cannot reopen a client instance, once it has been closed."
  1126. ),
  1127. }[self._state]
  1128. raise RuntimeError(msg)
  1129. self._state = ClientState.OPENED
  1130. self._transport.__enter__()
  1131. for transport in self._mounts.values():
  1132. if transport is not None:
  1133. transport.__enter__()
  1134. return self
  1135. def __exit__(
  1136. self,
  1137. exc_type: typing.Optional[typing.Type[BaseException]] = None,
  1138. exc_value: typing.Optional[BaseException] = None,
  1139. traceback: typing.Optional[TracebackType] = None,
  1140. ) -> None:
  1141. self._state = ClientState.CLOSED
  1142. self._transport.__exit__(exc_type, exc_value, traceback)
  1143. for transport in self._mounts.values():
  1144. if transport is not None:
  1145. transport.__exit__(exc_type, exc_value, traceback)
  1146. class AsyncClient(BaseClient):
  1147. """
  1148. An asynchronous HTTP client, with connection pooling, HTTP/2, redirects,
  1149. cookie persistence, etc.
  1150. Usage:
  1151. ```python
  1152. >>> async with httpx.AsyncClient() as client:
  1153. >>> response = await client.get('https://example.org')
  1154. ```
  1155. **Parameters:**
  1156. * **auth** - *(optional)* An authentication class to use when sending
  1157. requests.
  1158. * **params** - *(optional)* Query parameters to include in request URLs, as
  1159. a string, dictionary, or sequence of two-tuples.
  1160. * **headers** - *(optional)* Dictionary of HTTP headers to include when
  1161. sending requests.
  1162. * **cookies** - *(optional)* Dictionary of Cookie items to include when
  1163. sending requests.
  1164. * **verify** - *(optional)* SSL certificates (a.k.a CA bundle) used to
  1165. verify the identity of requested hosts. Either `True` (default CA bundle),
  1166. a path to an SSL certificate file, an `ssl.SSLContext`, or `False`
  1167. (which will disable verification).
  1168. * **cert** - *(optional)* An SSL certificate used by the requested host
  1169. to authenticate the client. Either a path to an SSL certificate file, or
  1170. two-tuple of (certificate file, key file), or a three-tuple of (certificate
  1171. file, key file, password).
  1172. * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
  1173. enabled. Defaults to `False`.
  1174. * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
  1175. * **proxies** - *(optional)* A dictionary mapping HTTP protocols to proxy
  1176. URLs.
  1177. * **timeout** - *(optional)* The timeout configuration to use when sending
  1178. requests.
  1179. * **limits** - *(optional)* The limits configuration to use.
  1180. * **max_redirects** - *(optional)* The maximum number of redirect responses
  1181. that should be followed.
  1182. * **base_url** - *(optional)* A URL to use as the base when building
  1183. request URLs.
  1184. * **transport** - *(optional)* A transport class to use for sending requests
  1185. over the network.
  1186. * **app** - *(optional)* An ASGI application to send requests to,
  1187. rather than sending actual network requests.
  1188. * **trust_env** - *(optional)* Enables or disables usage of environment
  1189. variables for configuration.
  1190. * **default_encoding** - *(optional)* The default encoding to use for decoding
  1191. response text, if no charset information is included in a response Content-Type
  1192. header. Set to a callable for automatic character set detection. Default: "utf-8".
  1193. """
  1194. def __init__(
  1195. self,
  1196. *,
  1197. auth: typing.Optional[AuthTypes] = None,
  1198. params: typing.Optional[QueryParamTypes] = None,
  1199. headers: typing.Optional[HeaderTypes] = None,
  1200. cookies: typing.Optional[CookieTypes] = None,
  1201. verify: VerifyTypes = True,
  1202. cert: typing.Optional[CertTypes] = None,
  1203. http1: bool = True,
  1204. http2: bool = False,
  1205. proxy: typing.Optional[ProxyTypes] = None,
  1206. proxies: typing.Optional[ProxiesTypes] = None,
  1207. mounts: typing.Optional[
  1208. typing.Mapping[str, typing.Optional[AsyncBaseTransport]]
  1209. ] = None,
  1210. timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
  1211. follow_redirects: bool = False,
  1212. limits: Limits = DEFAULT_LIMITS,
  1213. max_redirects: int = DEFAULT_MAX_REDIRECTS,
  1214. event_hooks: typing.Optional[
  1215. typing.Mapping[str, typing.List[typing.Callable[..., typing.Any]]]
  1216. ] = None,
  1217. base_url: URLTypes = "",
  1218. transport: typing.Optional[AsyncBaseTransport] = None,
  1219. app: typing.Optional[typing.Callable[..., typing.Any]] = None,
  1220. trust_env: bool = True,
  1221. default_encoding: typing.Union[str, typing.Callable[[bytes], str]] = "utf-8",
  1222. ) -> None:
  1223. super().__init__(
  1224. auth=auth,
  1225. params=params,
  1226. headers=headers,
  1227. cookies=cookies,
  1228. timeout=timeout,
  1229. follow_redirects=follow_redirects,
  1230. max_redirects=max_redirects,
  1231. event_hooks=event_hooks,
  1232. base_url=base_url,
  1233. trust_env=trust_env,
  1234. default_encoding=default_encoding,
  1235. )
  1236. if http2:
  1237. try:
  1238. import h2 # noqa
  1239. except ImportError: # pragma: no cover
  1240. raise ImportError(
  1241. "Using http2=True, but the 'h2' package is not installed. "
  1242. "Make sure to install httpx using `pip install httpx[http2]`."
  1243. ) from None
  1244. if proxies:
  1245. message = (
  1246. "The 'proxies' argument is now deprecated."
  1247. " Use 'proxy' or 'mounts' instead."
  1248. )
  1249. warnings.warn(message, DeprecationWarning)
  1250. if proxy:
  1251. raise RuntimeError("Use either `proxy` or 'proxies', not both.")
  1252. allow_env_proxies = trust_env and app is None and transport is None
  1253. proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies)
  1254. self._transport = self._init_transport(
  1255. verify=verify,
  1256. cert=cert,
  1257. http1=http1,
  1258. http2=http2,
  1259. limits=limits,
  1260. transport=transport,
  1261. app=app,
  1262. trust_env=trust_env,
  1263. )
  1264. self._mounts: typing.Dict[URLPattern, typing.Optional[AsyncBaseTransport]] = {
  1265. URLPattern(key): None
  1266. if proxy is None
  1267. else self._init_proxy_transport(
  1268. proxy,
  1269. verify=verify,
  1270. cert=cert,
  1271. http1=http1,
  1272. http2=http2,
  1273. limits=limits,
  1274. trust_env=trust_env,
  1275. )
  1276. for key, proxy in proxy_map.items()
  1277. }
  1278. if mounts is not None:
  1279. self._mounts.update(
  1280. {URLPattern(key): transport for key, transport in mounts.items()}
  1281. )
  1282. self._mounts = dict(sorted(self._mounts.items()))
  1283. def _init_transport(
  1284. self,
  1285. verify: VerifyTypes = True,
  1286. cert: typing.Optional[CertTypes] = None,
  1287. http1: bool = True,
  1288. http2: bool = False,
  1289. limits: Limits = DEFAULT_LIMITS,
  1290. transport: typing.Optional[AsyncBaseTransport] = None,
  1291. app: typing.Optional[typing.Callable[..., typing.Any]] = None,
  1292. trust_env: bool = True,
  1293. ) -> AsyncBaseTransport:
  1294. if transport is not None:
  1295. return transport
  1296. if app is not None:
  1297. return ASGITransport(app=app)
  1298. return AsyncHTTPTransport(
  1299. verify=verify,
  1300. cert=cert,
  1301. http1=http1,
  1302. http2=http2,
  1303. limits=limits,
  1304. trust_env=trust_env,
  1305. )
  1306. def _init_proxy_transport(
  1307. self,
  1308. proxy: Proxy,
  1309. verify: VerifyTypes = True,
  1310. cert: typing.Optional[CertTypes] = None,
  1311. http1: bool = True,
  1312. http2: bool = False,
  1313. limits: Limits = DEFAULT_LIMITS,
  1314. trust_env: bool = True,
  1315. ) -> AsyncBaseTransport:
  1316. return AsyncHTTPTransport(
  1317. verify=verify,
  1318. cert=cert,
  1319. http2=http2,
  1320. limits=limits,
  1321. trust_env=trust_env,
  1322. proxy=proxy,
  1323. )
  1324. def _transport_for_url(self, url: URL) -> AsyncBaseTransport:
  1325. """
  1326. Returns the transport instance that should be used for a given URL.
  1327. This will either be the standard connection pool, or a proxy.
  1328. """
  1329. for pattern, transport in self._mounts.items():
  1330. if pattern.matches(url):
  1331. return self._transport if transport is None else transport
  1332. return self._transport
  1333. async def request(
  1334. self,
  1335. method: str,
  1336. url: URLTypes,
  1337. *,
  1338. content: typing.Optional[RequestContent] = None,
  1339. data: typing.Optional[RequestData] = None,
  1340. files: typing.Optional[RequestFiles] = None,
  1341. json: typing.Optional[typing.Any] = None,
  1342. params: typing.Optional[QueryParamTypes] = None,
  1343. headers: typing.Optional[HeaderTypes] = None,
  1344. cookies: typing.Optional[CookieTypes] = None,
  1345. auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT,
  1346. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1347. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1348. extensions: typing.Optional[RequestExtensions] = None,
  1349. ) -> Response:
  1350. """
  1351. Build and send a request.
  1352. Equivalent to:
  1353. ```python
  1354. request = client.build_request(...)
  1355. response = await client.send(request, ...)
  1356. ```
  1357. See `AsyncClient.build_request()`, `AsyncClient.send()`
  1358. and [Merging of configuration][0] for how the various parameters
  1359. are merged with client-level configuration.
  1360. [0]: /advanced/#merging-of-configuration
  1361. """
  1362. request = self.build_request(
  1363. method=method,
  1364. url=url,
  1365. content=content,
  1366. data=data,
  1367. files=files,
  1368. json=json,
  1369. params=params,
  1370. headers=headers,
  1371. cookies=cookies,
  1372. timeout=timeout,
  1373. extensions=extensions,
  1374. )
  1375. return await self.send(request, auth=auth, follow_redirects=follow_redirects)
  1376. @asynccontextmanager
  1377. async def stream(
  1378. self,
  1379. method: str,
  1380. url: URLTypes,
  1381. *,
  1382. content: typing.Optional[RequestContent] = None,
  1383. data: typing.Optional[RequestData] = None,
  1384. files: typing.Optional[RequestFiles] = None,
  1385. json: typing.Optional[typing.Any] = None,
  1386. params: typing.Optional[QueryParamTypes] = None,
  1387. headers: typing.Optional[HeaderTypes] = None,
  1388. cookies: typing.Optional[CookieTypes] = None,
  1389. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1390. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1391. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1392. extensions: typing.Optional[RequestExtensions] = None,
  1393. ) -> typing.AsyncIterator[Response]:
  1394. """
  1395. Alternative to `httpx.request()` that streams the response body
  1396. instead of loading it into memory at once.
  1397. **Parameters**: See `httpx.request`.
  1398. See also: [Streaming Responses][0]
  1399. [0]: /quickstart#streaming-responses
  1400. """
  1401. request = self.build_request(
  1402. method=method,
  1403. url=url,
  1404. content=content,
  1405. data=data,
  1406. files=files,
  1407. json=json,
  1408. params=params,
  1409. headers=headers,
  1410. cookies=cookies,
  1411. timeout=timeout,
  1412. extensions=extensions,
  1413. )
  1414. response = await self.send(
  1415. request=request,
  1416. auth=auth,
  1417. follow_redirects=follow_redirects,
  1418. stream=True,
  1419. )
  1420. try:
  1421. yield response
  1422. finally:
  1423. await response.aclose()
  1424. async def send(
  1425. self,
  1426. request: Request,
  1427. *,
  1428. stream: bool = False,
  1429. auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT,
  1430. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1431. ) -> Response:
  1432. """
  1433. Send a request.
  1434. The request is sent as-is, unmodified.
  1435. Typically you'll want to build one with `AsyncClient.build_request()`
  1436. so that any client-level configuration is merged into the request,
  1437. but passing an explicit `httpx.Request()` is supported as well.
  1438. See also: [Request instances][0]
  1439. [0]: /advanced/#request-instances
  1440. """
  1441. if self._state == ClientState.CLOSED:
  1442. raise RuntimeError("Cannot send a request, as the client has been closed.")
  1443. self._state = ClientState.OPENED
  1444. follow_redirects = (
  1445. self.follow_redirects
  1446. if isinstance(follow_redirects, UseClientDefault)
  1447. else follow_redirects
  1448. )
  1449. auth = self._build_request_auth(request, auth)
  1450. response = await self._send_handling_auth(
  1451. request,
  1452. auth=auth,
  1453. follow_redirects=follow_redirects,
  1454. history=[],
  1455. )
  1456. try:
  1457. if not stream:
  1458. await response.aread()
  1459. return response
  1460. except BaseException as exc: # pragma: no cover
  1461. await response.aclose()
  1462. raise exc
  1463. async def _send_handling_auth(
  1464. self,
  1465. request: Request,
  1466. auth: Auth,
  1467. follow_redirects: bool,
  1468. history: typing.List[Response],
  1469. ) -> Response:
  1470. auth_flow = auth.async_auth_flow(request)
  1471. try:
  1472. request = await auth_flow.__anext__()
  1473. while True:
  1474. response = await self._send_handling_redirects(
  1475. request,
  1476. follow_redirects=follow_redirects,
  1477. history=history,
  1478. )
  1479. try:
  1480. try:
  1481. next_request = await auth_flow.asend(response)
  1482. except StopAsyncIteration:
  1483. return response
  1484. response.history = list(history)
  1485. await response.aread()
  1486. request = next_request
  1487. history.append(response)
  1488. except BaseException as exc:
  1489. await response.aclose()
  1490. raise exc
  1491. finally:
  1492. await auth_flow.aclose()
  1493. async def _send_handling_redirects(
  1494. self,
  1495. request: Request,
  1496. follow_redirects: bool,
  1497. history: typing.List[Response],
  1498. ) -> Response:
  1499. while True:
  1500. if len(history) > self.max_redirects:
  1501. raise TooManyRedirects(
  1502. "Exceeded maximum allowed redirects.", request=request
  1503. )
  1504. for hook in self._event_hooks["request"]:
  1505. await hook(request)
  1506. response = await self._send_single_request(request)
  1507. try:
  1508. for hook in self._event_hooks["response"]:
  1509. await hook(response)
  1510. response.history = list(history)
  1511. if not response.has_redirect_location:
  1512. return response
  1513. request = self._build_redirect_request(request, response)
  1514. history = history + [response]
  1515. if follow_redirects:
  1516. await response.aread()
  1517. else:
  1518. response.next_request = request
  1519. return response
  1520. except BaseException as exc:
  1521. await response.aclose()
  1522. raise exc
  1523. async def _send_single_request(self, request: Request) -> Response:
  1524. """
  1525. Sends a single request, without handling any redirections.
  1526. """
  1527. transport = self._transport_for_url(request.url)
  1528. timer = Timer()
  1529. await timer.async_start()
  1530. if not isinstance(request.stream, AsyncByteStream):
  1531. raise RuntimeError(
  1532. "Attempted to send an sync request with an AsyncClient instance."
  1533. )
  1534. with request_context(request=request):
  1535. response = await transport.handle_async_request(request)
  1536. assert isinstance(response.stream, AsyncByteStream)
  1537. response.request = request
  1538. response.stream = BoundAsyncStream(
  1539. response.stream, response=response, timer=timer
  1540. )
  1541. self.cookies.extract_cookies(response)
  1542. response.default_encoding = self._default_encoding
  1543. logger.info(
  1544. 'HTTP Request: %s %s "%s %d %s"',
  1545. request.method,
  1546. request.url,
  1547. response.http_version,
  1548. response.status_code,
  1549. response.reason_phrase,
  1550. )
  1551. return response
  1552. async def get(
  1553. self,
  1554. url: URLTypes,
  1555. *,
  1556. params: typing.Optional[QueryParamTypes] = None,
  1557. headers: typing.Optional[HeaderTypes] = None,
  1558. cookies: typing.Optional[CookieTypes] = None,
  1559. auth: typing.Union[AuthTypes, UseClientDefault, None] = USE_CLIENT_DEFAULT,
  1560. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1561. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1562. extensions: typing.Optional[RequestExtensions] = None,
  1563. ) -> Response:
  1564. """
  1565. Send a `GET` request.
  1566. **Parameters**: See `httpx.request`.
  1567. """
  1568. return await self.request(
  1569. "GET",
  1570. url,
  1571. params=params,
  1572. headers=headers,
  1573. cookies=cookies,
  1574. auth=auth,
  1575. follow_redirects=follow_redirects,
  1576. timeout=timeout,
  1577. extensions=extensions,
  1578. )
  1579. async def options(
  1580. self,
  1581. url: URLTypes,
  1582. *,
  1583. params: typing.Optional[QueryParamTypes] = None,
  1584. headers: typing.Optional[HeaderTypes] = None,
  1585. cookies: typing.Optional[CookieTypes] = None,
  1586. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1587. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1588. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1589. extensions: typing.Optional[RequestExtensions] = None,
  1590. ) -> Response:
  1591. """
  1592. Send an `OPTIONS` request.
  1593. **Parameters**: See `httpx.request`.
  1594. """
  1595. return await self.request(
  1596. "OPTIONS",
  1597. url,
  1598. params=params,
  1599. headers=headers,
  1600. cookies=cookies,
  1601. auth=auth,
  1602. follow_redirects=follow_redirects,
  1603. timeout=timeout,
  1604. extensions=extensions,
  1605. )
  1606. async def head(
  1607. self,
  1608. url: URLTypes,
  1609. *,
  1610. params: typing.Optional[QueryParamTypes] = None,
  1611. headers: typing.Optional[HeaderTypes] = None,
  1612. cookies: typing.Optional[CookieTypes] = None,
  1613. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1614. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1615. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1616. extensions: typing.Optional[RequestExtensions] = None,
  1617. ) -> Response:
  1618. """
  1619. Send a `HEAD` request.
  1620. **Parameters**: See `httpx.request`.
  1621. """
  1622. return await self.request(
  1623. "HEAD",
  1624. url,
  1625. params=params,
  1626. headers=headers,
  1627. cookies=cookies,
  1628. auth=auth,
  1629. follow_redirects=follow_redirects,
  1630. timeout=timeout,
  1631. extensions=extensions,
  1632. )
  1633. async def post(
  1634. self,
  1635. url: URLTypes,
  1636. *,
  1637. content: typing.Optional[RequestContent] = None,
  1638. data: typing.Optional[RequestData] = None,
  1639. files: typing.Optional[RequestFiles] = None,
  1640. json: typing.Optional[typing.Any] = None,
  1641. params: typing.Optional[QueryParamTypes] = None,
  1642. headers: typing.Optional[HeaderTypes] = None,
  1643. cookies: typing.Optional[CookieTypes] = None,
  1644. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1645. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1646. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1647. extensions: typing.Optional[RequestExtensions] = None,
  1648. ) -> Response:
  1649. """
  1650. Send a `POST` request.
  1651. **Parameters**: See `httpx.request`.
  1652. """
  1653. return await self.request(
  1654. "POST",
  1655. url,
  1656. content=content,
  1657. data=data,
  1658. files=files,
  1659. json=json,
  1660. params=params,
  1661. headers=headers,
  1662. cookies=cookies,
  1663. auth=auth,
  1664. follow_redirects=follow_redirects,
  1665. timeout=timeout,
  1666. extensions=extensions,
  1667. )
  1668. async def put(
  1669. self,
  1670. url: URLTypes,
  1671. *,
  1672. content: typing.Optional[RequestContent] = None,
  1673. data: typing.Optional[RequestData] = None,
  1674. files: typing.Optional[RequestFiles] = None,
  1675. json: typing.Optional[typing.Any] = None,
  1676. params: typing.Optional[QueryParamTypes] = None,
  1677. headers: typing.Optional[HeaderTypes] = None,
  1678. cookies: typing.Optional[CookieTypes] = None,
  1679. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1680. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1681. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1682. extensions: typing.Optional[RequestExtensions] = None,
  1683. ) -> Response:
  1684. """
  1685. Send a `PUT` request.
  1686. **Parameters**: See `httpx.request`.
  1687. """
  1688. return await self.request(
  1689. "PUT",
  1690. url,
  1691. content=content,
  1692. data=data,
  1693. files=files,
  1694. json=json,
  1695. params=params,
  1696. headers=headers,
  1697. cookies=cookies,
  1698. auth=auth,
  1699. follow_redirects=follow_redirects,
  1700. timeout=timeout,
  1701. extensions=extensions,
  1702. )
  1703. async def patch(
  1704. self,
  1705. url: URLTypes,
  1706. *,
  1707. content: typing.Optional[RequestContent] = None,
  1708. data: typing.Optional[RequestData] = None,
  1709. files: typing.Optional[RequestFiles] = None,
  1710. json: typing.Optional[typing.Any] = None,
  1711. params: typing.Optional[QueryParamTypes] = None,
  1712. headers: typing.Optional[HeaderTypes] = None,
  1713. cookies: typing.Optional[CookieTypes] = None,
  1714. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1715. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1716. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1717. extensions: typing.Optional[RequestExtensions] = None,
  1718. ) -> Response:
  1719. """
  1720. Send a `PATCH` request.
  1721. **Parameters**: See `httpx.request`.
  1722. """
  1723. return await self.request(
  1724. "PATCH",
  1725. url,
  1726. content=content,
  1727. data=data,
  1728. files=files,
  1729. json=json,
  1730. params=params,
  1731. headers=headers,
  1732. cookies=cookies,
  1733. auth=auth,
  1734. follow_redirects=follow_redirects,
  1735. timeout=timeout,
  1736. extensions=extensions,
  1737. )
  1738. async def delete(
  1739. self,
  1740. url: URLTypes,
  1741. *,
  1742. params: typing.Optional[QueryParamTypes] = None,
  1743. headers: typing.Optional[HeaderTypes] = None,
  1744. cookies: typing.Optional[CookieTypes] = None,
  1745. auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1746. follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
  1747. timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
  1748. extensions: typing.Optional[RequestExtensions] = None,
  1749. ) -> Response:
  1750. """
  1751. Send a `DELETE` request.
  1752. **Parameters**: See `httpx.request`.
  1753. """
  1754. return await self.request(
  1755. "DELETE",
  1756. url,
  1757. params=params,
  1758. headers=headers,
  1759. cookies=cookies,
  1760. auth=auth,
  1761. follow_redirects=follow_redirects,
  1762. timeout=timeout,
  1763. extensions=extensions,
  1764. )
  1765. async def aclose(self) -> None:
  1766. """
  1767. Close transport and proxies.
  1768. """
  1769. if self._state != ClientState.CLOSED:
  1770. self._state = ClientState.CLOSED
  1771. await self._transport.aclose()
  1772. for proxy in self._mounts.values():
  1773. if proxy is not None:
  1774. await proxy.aclose()
  1775. async def __aenter__(self: U) -> U:
  1776. if self._state != ClientState.UNOPENED:
  1777. msg = {
  1778. ClientState.OPENED: "Cannot open a client instance more than once.",
  1779. ClientState.CLOSED: (
  1780. "Cannot reopen a client instance, once it has been closed."
  1781. ),
  1782. }[self._state]
  1783. raise RuntimeError(msg)
  1784. self._state = ClientState.OPENED
  1785. await self._transport.__aenter__()
  1786. for proxy in self._mounts.values():
  1787. if proxy is not None:
  1788. await proxy.__aenter__()
  1789. return self
  1790. async def __aexit__(
  1791. self,
  1792. exc_type: typing.Optional[typing.Type[BaseException]] = None,
  1793. exc_value: typing.Optional[BaseException] = None,
  1794. traceback: typing.Optional[TracebackType] = None,
  1795. ) -> None:
  1796. self._state = ClientState.CLOSED
  1797. await self._transport.__aexit__(exc_type, exc_value, traceback)
  1798. for proxy in self._mounts.values():
  1799. if proxy is not None:
  1800. await proxy.__aexit__(exc_type, exc_value, traceback)