pyqwest 0.2.0__cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyqwest/__init__.py ADDED
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = [
4
+ "Client",
5
+ "FullResponse",
6
+ "HTTPTransport",
7
+ "HTTPVersion",
8
+ "Headers",
9
+ "ReadError",
10
+ "Request",
11
+ "Response",
12
+ "StreamError",
13
+ "StreamErrorCode",
14
+ "SyncClient",
15
+ "SyncHTTPTransport",
16
+ "SyncRequest",
17
+ "SyncResponse",
18
+ "SyncTransport",
19
+ "Transport",
20
+ "WriteError",
21
+ "get_default_sync_transport",
22
+ "get_default_transport",
23
+ ]
24
+
25
+ from . import _pyqwest
26
+ from ._coro import Client, Response
27
+ from ._pyqwest import (
28
+ FullResponse,
29
+ Headers,
30
+ HTTPTransport,
31
+ HTTPVersion,
32
+ ReadError,
33
+ Request,
34
+ StreamError,
35
+ StreamErrorCode,
36
+ SyncClient,
37
+ SyncHTTPTransport,
38
+ SyncRequest,
39
+ SyncResponse,
40
+ SyncTransport,
41
+ Transport,
42
+ WriteError,
43
+ get_default_sync_transport,
44
+ get_default_transport,
45
+ )
46
+
47
+ __doc__ = _pyqwest.__doc__
pyqwest/_coro.py ADDED
@@ -0,0 +1,231 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import asynccontextmanager
4
+ from typing import TYPE_CHECKING
5
+
6
+ from ._pyqwest import Client as NativeClient
7
+ from ._pyqwest import FullResponse, Headers, Response, Transport
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import AsyncIterator, Iterable, Mapping
11
+
12
+ # We expose plain-Python wrappers for the async methods as the easiest way
13
+ # of making them coroutines rather than methods that return Futures,
14
+ # which is more Pythonic.
15
+
16
+
17
+ class Client:
18
+ """An asynchronous HTTP client.
19
+
20
+ A client is a lightweight wrapper around a Transport, providing convenience methods
21
+ for common HTTP operations with buffering.
22
+ """
23
+
24
+ _client: NativeClient
25
+
26
+ def __init__(self, transport: Transport | None = None) -> None:
27
+ """Creates a new asynchronous HTTP client.
28
+
29
+ Args:
30
+ transport: The transport to use for requests. If None, the shared default
31
+ transport will be used.
32
+ """
33
+ self._client = NativeClient(transport=transport)
34
+
35
+ async def get(
36
+ self,
37
+ url: str,
38
+ headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
39
+ timeout: float | None = None,
40
+ ) -> FullResponse:
41
+ """Executes a GET HTTP request.
42
+
43
+ Args:
44
+ url: The unencoded request URL.
45
+ headers: The request headers.
46
+ timeout: The timeout for the request in seconds.
47
+
48
+ Raises:
49
+ ConnectionError: If the connection fails.
50
+ TimeoutError: If the request times out.
51
+ """
52
+ return await self._client.get(url, headers=headers, timeout=timeout)
53
+
54
+ async def post(
55
+ self,
56
+ url: str,
57
+ headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
58
+ content: bytes | AsyncIterator[bytes] | None = None,
59
+ timeout: float | None = None,
60
+ ) -> FullResponse:
61
+ """Executes a POST HTTP request.
62
+
63
+ Args:
64
+ url: The unencoded request URL.
65
+ headers: The request headers.
66
+ content: The request content.
67
+ timeout: The timeout for the request in seconds.
68
+
69
+ Raises:
70
+ ConnectionError: If the connection fails.
71
+ TimeoutError: If the request times out.
72
+ """
73
+ return await self._client.post(
74
+ url, headers=headers, content=content, timeout=timeout
75
+ )
76
+
77
+ async def delete(
78
+ self,
79
+ url: str,
80
+ headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
81
+ timeout: float | None = None,
82
+ ) -> FullResponse:
83
+ """Executes a DELETE HTTP request.
84
+
85
+ Args:
86
+ url: The unencoded request URL.
87
+ headers: The request headers.
88
+ timeout: The timeout for the request in seconds.
89
+
90
+ Raises:
91
+ ConnectionError: If the connection fails.
92
+ TimeoutError: If the request times out.
93
+ """
94
+ return await self._client.delete(url, headers=headers, timeout=timeout)
95
+
96
+ async def head(
97
+ self,
98
+ url: str,
99
+ headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
100
+ timeout: float | None = None,
101
+ ) -> FullResponse:
102
+ """Executes a HEAD HTTP request.
103
+
104
+ Args:
105
+ url: The unencoded request URL.
106
+ headers: The request headers.
107
+ timeout: The timeout for the request in seconds.
108
+
109
+ Raises:
110
+ ConnectionError: If the connection fails.
111
+ TimeoutError: If the request times out.
112
+ """
113
+ return await self._client.head(url, headers=headers, timeout=timeout)
114
+
115
+ async def options(
116
+ self,
117
+ url: str,
118
+ headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
119
+ timeout: float | None = None,
120
+ ) -> FullResponse:
121
+ """Executes a OPTIONS HTTP request.
122
+
123
+ Args:
124
+ url: The unencoded request URL.
125
+ headers: The request headers.
126
+ timeout: The timeout for the request in seconds.
127
+
128
+ Raises:
129
+ ConnectionError: If the connection fails.
130
+ TimeoutError: If the request times out.
131
+ """
132
+ return await self._client.options(url, headers=headers, timeout=timeout)
133
+
134
+ async def patch(
135
+ self,
136
+ url: str,
137
+ headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
138
+ content: bytes | AsyncIterator[bytes] | None = None,
139
+ timeout: float | None = None,
140
+ ) -> FullResponse:
141
+ """Executes a PATCH HTTP request.
142
+
143
+ Args:
144
+ url: The unencoded request URL.
145
+ headers: The request headers.
146
+ content: The request content.
147
+ timeout: The timeout for the request in seconds.
148
+
149
+ Raises:
150
+ ConnectionError: If the connection fails.
151
+ TimeoutError: If the request times out.
152
+ """
153
+ return await self._client.patch(
154
+ url, headers=headers, content=content, timeout=timeout
155
+ )
156
+
157
+ async def put(
158
+ self,
159
+ url: str,
160
+ headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
161
+ content: bytes | AsyncIterator[bytes] | None = None,
162
+ timeout: float | None = None,
163
+ ) -> FullResponse:
164
+ """Executes a PUT HTTP request.
165
+
166
+ Args:
167
+ url: The unencoded request URL.
168
+ headers: The request headers.
169
+ content: The request content.
170
+ timeout: The timeout for the request in seconds.
171
+
172
+ Raises:
173
+ ConnectionError: If the connection fails.
174
+ TimeoutError: If the request times out.
175
+ """
176
+ return await self._client.put(
177
+ url, headers=headers, content=content, timeout=timeout
178
+ )
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: bytes | AsyncIterator[bytes] | None = None,
186
+ timeout: float | None = None,
187
+ ) -> FullResponse:
188
+ """Executes an HTTP request, returning the full buffered response.
189
+
190
+ Args:
191
+ method: The HTTP method.
192
+ url: The unencoded request URL.
193
+ headers: The request headers.
194
+ content: The request content.
195
+ timeout: The timeout for the request in seconds.
196
+
197
+ Raises:
198
+ ConnectionError: If the connection fails.
199
+ TimeoutError: If the request times out.
200
+ """
201
+ return await self._client.execute(
202
+ method, url, headers=headers, content=content, timeout=timeout
203
+ )
204
+
205
+ @asynccontextmanager
206
+ async def stream(
207
+ self,
208
+ method: str,
209
+ url: str,
210
+ headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
211
+ content: bytes | AsyncIterator[bytes] | None = None,
212
+ timeout: float | None = None,
213
+ ) -> AsyncIterator[Response]:
214
+ """Executes an HTTP request, allowing the response content to be streamed.
215
+
216
+ Args:
217
+ method: The HTTP method.
218
+ url: The unencoded request URL.
219
+ headers: The request headers.
220
+ content: The request content.
221
+ timeout: The timeout for the request in seconds.
222
+
223
+ Raises:
224
+ ConnectionError: If the connection fails.
225
+ TimeoutError: If the request times out.
226
+ """
227
+ response = await self._client.stream(
228
+ method, url, headers=headers, content=content, timeout=timeout
229
+ )
230
+ async with response:
231
+ yield response
pyqwest/_glue.py ADDED
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from typing import TYPE_CHECKING, Protocol, TypeVar
5
+
6
+ from ._pyqwest import FullResponse, Headers, Request, Transport
7
+
8
+ if TYPE_CHECKING:
9
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
10
+
11
+ T_contra = TypeVar("T_contra", contravariant=True)
12
+ U = TypeVar("U")
13
+
14
+
15
+ async def wrap_body_gen(
16
+ gen: AsyncIterator[T_contra], wrap_fn: Callable[[T_contra], U]
17
+ ) -> AsyncIterator[U]:
18
+ try:
19
+ async for item in gen:
20
+ yield wrap_fn(item)
21
+ finally:
22
+ try:
23
+ aclose = gen.aclose # type: ignore[attr-defined]
24
+ except AttributeError:
25
+ pass
26
+ else:
27
+ await aclose()
28
+
29
+
30
+ async def new_full_response(
31
+ status: int,
32
+ headers: Headers,
33
+ content: AsyncIterator[memoryview | bytes | bytearray],
34
+ trailers: Headers,
35
+ ) -> FullResponse:
36
+ buf = bytearray()
37
+ try:
38
+ async for chunk in content:
39
+ buf.extend(chunk)
40
+ finally:
41
+ try:
42
+ aclose = content.aclose # type: ignore[attr-defined]
43
+ except AttributeError:
44
+ pass
45
+ else:
46
+ await aclose()
47
+ return FullResponse(status, headers, bytes(buf), trailers)
48
+
49
+
50
+ async def execute_and_read_full(transport: Transport, request: Request) -> FullResponse:
51
+ resp = await transport.execute(request)
52
+ return await new_full_response(
53
+ resp.status, resp.headers, resp.content, resp.trailers
54
+ )
55
+
56
+
57
+ def read_content_sync(content: Iterator[bytes | memoryview]) -> bytes:
58
+ buf = bytearray()
59
+ try:
60
+ for chunk in content:
61
+ buf.extend(chunk)
62
+ finally:
63
+ try:
64
+ close = content.close # type: ignore[attr-defined]
65
+ except AttributeError:
66
+ pass
67
+ else:
68
+ close()
69
+ return bytes(buf)
70
+
71
+
72
+ # Vendored from pyo3-async-runtimes to apply some fixes
73
+
74
+
75
+ class Sender(Protocol[T_contra]):
76
+ def send(self, item: T_contra | BaseException) -> bool | Awaitable[bool]: ...
77
+
78
+ def close(self) -> None: ...
79
+
80
+
81
+ async def forward(gen: AsyncIterator[T_contra], sender: Sender[T_contra]) -> None:
82
+ try:
83
+ async for item in gen:
84
+ should_continue = sender.send(item)
85
+
86
+ if inspect.isawaitable(should_continue):
87
+ should_continue = await should_continue
88
+
89
+ if should_continue:
90
+ continue
91
+ break
92
+ except Exception as e:
93
+ res = sender.send(e)
94
+ if inspect.isawaitable(res):
95
+ await res
96
+ finally:
97
+ sender.close()