_coro.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. from __future__ import annotations
  2. from contextlib import asynccontextmanager
  3. from typing import TYPE_CHECKING
  4. from ._pyqwest import Client as NativeClient
  5. from ._pyqwest import FullResponse, Headers, Response, Transport
  6. if TYPE_CHECKING:
  7. from collections.abc import AsyncIterator, Iterable, Mapping
  8. from ._pyqwest import _QueryParams, _RequestContent
  9. # We expose plain-Python wrappers for the async methods as the easiest way
  10. # of making them coroutines rather than methods that return Futures,
  11. # which is more Pythonic.
  12. class Client:
  13. """An asynchronous HTTP client.
  14. A client is a lightweight wrapper around a Transport, providing convenience methods
  15. for common HTTP operations with buffering.
  16. The asynchronous client does not expose per-request timeouts on its methods.
  17. Use `asyncio.wait_for` or similar to enforce timeouts per-requests or initialize
  18. `HTTPTransport` with a default timeout.
  19. """
  20. _client: NativeClient
  21. def __init__(self, transport: Transport | None = None) -> None:
  22. """Creates a new asynchronous HTTP client.
  23. Args:
  24. transport: The transport to use for requests. If None, the shared default
  25. transport will be used.
  26. """
  27. self._client = NativeClient(transport=transport)
  28. async def get(
  29. self,
  30. url: str,
  31. headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
  32. *,
  33. params: _QueryParams | None = None,
  34. ) -> FullResponse:
  35. """Executes a GET HTTP request.
  36. Args:
  37. url: The unencoded request URL.
  38. headers: The request headers.
  39. params: Query parameters to append to the URL. None values will be treated as key-only.
  40. Raises:
  41. ConnectionError: If the connection fails.
  42. TimeoutError: If the request times out.
  43. RemoteProtocolError: If the peer violates the HTTP protocol.
  44. ReadError: If an error occurs reading the response.
  45. WriteError: If an error occurs writing the request.
  46. """
  47. return await self._client.get(url, headers=headers, params=params)
  48. async def post(
  49. self,
  50. url: str,
  51. headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
  52. content: _RequestContent | None = None,
  53. *,
  54. params: _QueryParams | None = None,
  55. ) -> FullResponse:
  56. """Executes a POST HTTP request.
  57. Args:
  58. url: The unencoded request URL.
  59. headers: The request headers.
  60. content: The request content. A Python dictionary will be converted to JSON.
  61. params: Query parameters to append to the URL. None values will be treated as key-only.
  62. Raises:
  63. ConnectionError: If the connection fails.
  64. TimeoutError: If the request times out.
  65. RemoteProtocolError: If the peer violates the HTTP protocol.
  66. ReadError: If an error occurs reading the response.
  67. WriteError: If an error occurs writing the request.
  68. """
  69. return await self._client.post(
  70. url, headers=headers, content=content, params=params
  71. )
  72. async def delete(
  73. self,
  74. url: str,
  75. headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
  76. *,
  77. params: _QueryParams | None = None,
  78. ) -> FullResponse:
  79. """Executes a DELETE HTTP request.
  80. Args:
  81. url: The unencoded request URL.
  82. headers: The request headers.
  83. params: Query parameters to append to the URL. None values will be treated as key-only.
  84. Raises:
  85. ConnectionError: If the connection fails.
  86. TimeoutError: If the request times out.
  87. RemoteProtocolError: If the peer violates the HTTP protocol.
  88. ReadError: If an error occurs reading the response.
  89. WriteError: If an error occurs writing the request.
  90. """
  91. return await self._client.delete(url, headers=headers, params=params)
  92. async def head(
  93. self,
  94. url: str,
  95. headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
  96. *,
  97. params: _QueryParams | None = None,
  98. ) -> FullResponse:
  99. """Executes a HEAD HTTP request.
  100. Args:
  101. url: The unencoded request URL.
  102. headers: The request headers.
  103. params: Query parameters to append to the URL. None values will be treated as key-only.
  104. Raises:
  105. ConnectionError: If the connection fails.
  106. TimeoutError: If the request times out.
  107. RemoteProtocolError: If the peer violates the HTTP protocol.
  108. ReadError: If an error occurs reading the response.
  109. WriteError: If an error occurs writing the request.
  110. """
  111. return await self._client.head(url, headers=headers, params=params)
  112. async def options(
  113. self,
  114. url: str,
  115. headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
  116. *,
  117. params: _QueryParams | None = None,
  118. ) -> FullResponse:
  119. """Executes a OPTIONS HTTP request.
  120. Args:
  121. url: The unencoded request URL.
  122. headers: The request headers.
  123. params: Query parameters to append to the URL. None values will be treated as key-only.
  124. Raises:
  125. ConnectionError: If the connection fails.
  126. TimeoutError: If the request times out.
  127. RemoteProtocolError: If the peer violates the HTTP protocol.
  128. ReadError: If an error occurs reading the response.
  129. WriteError: If an error occurs writing the request.
  130. """
  131. return await self._client.options(url, headers=headers, params=params)
  132. async def patch(
  133. self,
  134. url: str,
  135. headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
  136. content: _RequestContent | None = None,
  137. *,
  138. params: _QueryParams | None = None,
  139. ) -> FullResponse:
  140. """Executes a PATCH HTTP request.
  141. Args:
  142. url: The unencoded request URL.
  143. headers: The request headers.
  144. content: The request content. A Python dictionary will be converted to JSON.
  145. params: Query parameters to append to the URL. None values will be treated as key-only.
  146. Raises:
  147. ConnectionError: If the connection fails.
  148. TimeoutError: If the request times out.
  149. RemoteProtocolError: If the peer violates the HTTP protocol.
  150. ReadError: If an error occurs reading the response.
  151. WriteError: If an error occurs writing the request.
  152. """
  153. return await self._client.patch(
  154. url, headers=headers, content=content, params=params
  155. )
  156. async def put(
  157. self,
  158. url: str,
  159. headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
  160. content: _RequestContent | None = None,
  161. *,
  162. params: _QueryParams | None = None,
  163. ) -> FullResponse:
  164. """Executes a PUT HTTP request.
  165. Args:
  166. url: The unencoded request URL.
  167. headers: The request headers.
  168. content: The request content. A Python dictionary will be converted to JSON.
  169. params: Query parameters to append to the URL. None values will be treated as key-only.
  170. Raises:
  171. ConnectionError: If the connection fails.
  172. TimeoutError: If the request times out.
  173. RemoteProtocolError: If the peer violates the HTTP protocol.
  174. ReadError: If an error occurs reading the response.
  175. WriteError: If an error occurs writing the request.
  176. """
  177. return await self._client.put(
  178. url, headers=headers, content=content, params=params
  179. )
  180. async def execute(
  181. self,
  182. method: str,
  183. url: str,
  184. headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
  185. content: _RequestContent | None = None,
  186. *,
  187. params: _QueryParams | None = None,
  188. ) -> FullResponse:
  189. """Executes an HTTP request, returning the full buffered response.
  190. Args:
  191. method: The HTTP method.
  192. url: The unencoded request URL.
  193. headers: The request headers.
  194. content: The request content. A Python dictionary will be converted to JSON.
  195. params: Query parameters to append to the URL. None values will be treated as key-only.
  196. Raises:
  197. ConnectionError: If the connection fails.
  198. TimeoutError: If the request times out.
  199. RemoteProtocolError: If the peer violates the HTTP protocol.
  200. ReadError: If an error occurs reading the response.
  201. WriteError: If an error occurs writing the request.
  202. """
  203. return await self._client.execute(
  204. method, url, headers=headers, content=content, params=params
  205. )
  206. @asynccontextmanager
  207. async def stream(
  208. self,
  209. method: str,
  210. url: str,
  211. headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
  212. content: _RequestContent | None = None,
  213. *,
  214. params: _QueryParams | None = None,
  215. ) -> AsyncIterator[Response]:
  216. """Executes an HTTP request, allowing the response content to be streamed.
  217. Args:
  218. method: The HTTP method.
  219. url: The unencoded request URL.
  220. headers: The request headers.
  221. content: The request content. A Python dictionary will be converted to JSON.
  222. params: Query parameters to append to the URL. None values will be treated as key-only.
  223. Raises:
  224. ConnectionError: If the connection fails.
  225. TimeoutError: If the request times out.
  226. RemoteProtocolError: If the peer violates the HTTP protocol.
  227. ReadError: If an error occurs reading the response.
  228. WriteError: If an error occurs writing the request.
  229. """
  230. response = await self._client.stream(
  231. method, url, headers=headers, content=content, params=params
  232. )
  233. async with response:
  234. yield response