pyqwest 0.2.0__cp313-cp313-win_amd64.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 +47 -0
- pyqwest/_coro.py +231 -0
- pyqwest/_glue.py +97 -0
- pyqwest/_pyqwest.cp313-win_amd64.pyd +0 -0
- pyqwest/_pyqwest.pyi +1132 -0
- pyqwest/httpx/__init__.py +5 -0
- pyqwest/httpx/_transport.py +281 -0
- pyqwest/py.typed +0 -0
- pyqwest/testing/__init__.py +6 -0
- pyqwest/testing/_asgi.py +366 -0
- pyqwest/testing/_asgi_compatibility.py +80 -0
- pyqwest/testing/_wsgi.py +383 -0
- pyqwest-0.2.0.dist-info/METADATA +21 -0
- pyqwest-0.2.0.dist-info/RECORD +16 -0
- pyqwest-0.2.0.dist-info/WHEEL +4 -0
- pyqwest-0.2.0.dist-info/licenses/LICENSE +21 -0
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()
|
|
Binary file
|