pyreqwest 0.5.0__cp311-cp311-macosx_10_12_x86_64.whl → 0.6.0__cp311-cp311-macosx_10_12_x86_64.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.
- pyreqwest/_pyreqwest.cpython-311-darwin.so +0 -0
- pyreqwest/compatibility/__init__.py +4 -0
- pyreqwest/compatibility/httpx/__init__.py +11 -0
- pyreqwest/compatibility/httpx/_internal.py +60 -0
- pyreqwest/compatibility/httpx/transport.py +154 -0
- pyreqwest/pytest_plugin/types.py +1 -1
- {pyreqwest-0.5.0.dist-info → pyreqwest-0.6.0.dist-info}/METADATA +7 -1
- {pyreqwest-0.5.0.dist-info → pyreqwest-0.6.0.dist-info}/RECORD +11 -7
- {pyreqwest-0.5.0.dist-info → pyreqwest-0.6.0.dist-info}/WHEEL +1 -1
- {pyreqwest-0.5.0.dist-info → pyreqwest-0.6.0.dist-info}/entry_points.txt +0 -0
- {pyreqwest-0.5.0.dist-info → pyreqwest-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""httpx compatibility layer"""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import httpx # noqa: F401
|
|
5
|
+
except ImportError as e:
|
|
6
|
+
err = "httpx is not installed. Please install httpx to use the httpx compatibility layer."
|
|
7
|
+
raise ImportError(err) from e
|
|
8
|
+
|
|
9
|
+
from .transport import HttpxTransport, SyncHttpxTransport
|
|
10
|
+
|
|
11
|
+
__all__ = ["HttpxTransport", "SyncHttpxTransport"]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
from typing import TypeVar
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from pyreqwest import exceptions
|
|
7
|
+
from pyreqwest.exceptions import PyreqwestError
|
|
8
|
+
from pyreqwest.request import BaseRequestBuilder
|
|
9
|
+
from pyreqwest.response import BaseResponse
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T", bound=BaseRequestBuilder)
|
|
12
|
+
|
|
13
|
+
EXCEPTION_MAPPING: dict[type[PyreqwestError], type[httpx.RequestError]] = {
|
|
14
|
+
exceptions.ConnectTimeoutError: httpx.ConnectTimeout,
|
|
15
|
+
exceptions.ReadTimeoutError: httpx.ReadTimeout,
|
|
16
|
+
exceptions.WriteTimeoutError: httpx.WriteTimeout,
|
|
17
|
+
exceptions.PoolTimeoutError: httpx.PoolTimeout,
|
|
18
|
+
exceptions.RequestTimeoutError: httpx.TimeoutException,
|
|
19
|
+
exceptions.ConnectError: httpx.ConnectError,
|
|
20
|
+
exceptions.ReadError: httpx.ReadError,
|
|
21
|
+
exceptions.WriteError: httpx.WriteError,
|
|
22
|
+
exceptions.NetworkError: httpx.NetworkError,
|
|
23
|
+
exceptions.DecodeError: httpx.DecodingError,
|
|
24
|
+
exceptions.RedirectError: httpx.TooManyRedirects,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_httpx_response(
|
|
29
|
+
response: BaseResponse, response_stream: httpx.SyncByteStream | httpx.AsyncByteStream | None
|
|
30
|
+
) -> httpx.Response:
|
|
31
|
+
return httpx.Response(
|
|
32
|
+
status_code=response.status,
|
|
33
|
+
headers=list(response.headers.items()),
|
|
34
|
+
stream=response_stream,
|
|
35
|
+
extensions={"pyreqwest_response": response},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def map_extensions(builder: T, request: httpx.Request) -> T:
|
|
40
|
+
if not (ext := request.extensions):
|
|
41
|
+
return builder
|
|
42
|
+
|
|
43
|
+
# Handle timeout
|
|
44
|
+
# reqwest differs https://docs.rs/reqwest/latest/reqwest/struct.RequestBuilder.html#method.timeout
|
|
45
|
+
# For more granular control, users should configure the pyreqwest.Client directly.
|
|
46
|
+
if (timeout := ext.get("timeout")) and isinstance(timeout, dict) and (tot_timeout := sum(timeout.values())) > 0:
|
|
47
|
+
builder = builder.timeout(timedelta(seconds=tot_timeout))
|
|
48
|
+
|
|
49
|
+
return builder.extensions(ext)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def map_exception(exc: PyreqwestError, request: httpx.Request) -> httpx.RequestError | None:
|
|
53
|
+
if exact := EXCEPTION_MAPPING.get(type(exc)):
|
|
54
|
+
return exact(exc.message, request=request)
|
|
55
|
+
|
|
56
|
+
for pyreqwest_exc, httpx_exc in EXCEPTION_MAPPING.items():
|
|
57
|
+
if isinstance(exc, pyreqwest_exc):
|
|
58
|
+
return httpx_exc(exc.message, request=request)
|
|
59
|
+
|
|
60
|
+
return None
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Compatibility layer for httpx, which allows pyreqwest to replace httpcore transport for httpx."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator, Iterator
|
|
4
|
+
from contextlib import AsyncExitStack, ExitStack
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from pyreqwest.client import Client, ClientBuilder, SyncClient, SyncClientBuilder
|
|
9
|
+
from pyreqwest.exceptions import PyreqwestError
|
|
10
|
+
from pyreqwest.request import RequestBuilder, SyncRequestBuilder
|
|
11
|
+
from pyreqwest.response import Response, SyncResponse
|
|
12
|
+
|
|
13
|
+
from ._internal import build_httpx_response, map_exception, map_extensions
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HttpxTransport(httpx.AsyncBaseTransport):
|
|
17
|
+
"""httpx transport that uses pyreqwest for HTTP requests.
|
|
18
|
+
|
|
19
|
+
Example usage:
|
|
20
|
+
```python
|
|
21
|
+
import httpx
|
|
22
|
+
from pyreqwest.compatibility.httpx import HttpxTransport
|
|
23
|
+
|
|
24
|
+
async with httpx.AsyncClient(transport=HttpxTransport()) as httpx_client:
|
|
25
|
+
print(await httpx_client.get("https://example.com"))
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, client: Client | None = None, *, close_client: bool = True) -> None:
|
|
30
|
+
"""Initialize the HttpxTransport.
|
|
31
|
+
:param client: An optional pyreqwest Client instance. If not provided, a default Client will be created.
|
|
32
|
+
:param close_client: Whether to close the provided Client when the transport is closed.
|
|
33
|
+
"""
|
|
34
|
+
self._client: Client = client or ClientBuilder().build()
|
|
35
|
+
self._close_client = (client is None) or close_client
|
|
36
|
+
|
|
37
|
+
async def aclose(self) -> None:
|
|
38
|
+
"""Close the underlying pyreqwest Client if needed."""
|
|
39
|
+
if self._close_client:
|
|
40
|
+
await self._client.close()
|
|
41
|
+
|
|
42
|
+
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
|
43
|
+
"""AsyncBaseTransport implementation to handle httpx request using pyreqwest."""
|
|
44
|
+
req_builder = self._client.request(request.method, str(request.url)).headers(request.headers.multi_items())
|
|
45
|
+
req_builder = self._map_body(req_builder, request)
|
|
46
|
+
req_builder = map_extensions(req_builder, request)
|
|
47
|
+
return await self._map_response(req_builder, request)
|
|
48
|
+
|
|
49
|
+
def _map_body(self, builder: RequestBuilder, request: httpx.Request) -> RequestBuilder:
|
|
50
|
+
try:
|
|
51
|
+
return builder.body_bytes(request.content)
|
|
52
|
+
except httpx.RequestNotRead:
|
|
53
|
+
return builder.body_stream(request.stream)
|
|
54
|
+
|
|
55
|
+
async def _map_response(self, builder: RequestBuilder, request: httpx.Request) -> httpx.Response:
|
|
56
|
+
req_exit_stack = AsyncExitStack()
|
|
57
|
+
try:
|
|
58
|
+
response = await req_exit_stack.enter_async_context(builder.build_streamed())
|
|
59
|
+
return build_httpx_response(response, response_stream=ResponseStream(response, req_exit_stack))
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
await req_exit_stack.aclose()
|
|
62
|
+
if isinstance(exc, PyreqwestError) and (mapped := map_exception(exc, request)):
|
|
63
|
+
raise mapped from exc
|
|
64
|
+
raise
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SyncHttpxTransport(httpx.BaseTransport):
|
|
68
|
+
"""httpx transport that uses pyreqwest for HTTP requests.
|
|
69
|
+
|
|
70
|
+
Example usage:
|
|
71
|
+
```python
|
|
72
|
+
import httpx
|
|
73
|
+
from pyreqwest.compatibility.httpx import SyncHttpxTransport
|
|
74
|
+
|
|
75
|
+
with httpx.Client(transport=SyncHttpxTransport()) as httpx_client:
|
|
76
|
+
print(httpx_client.get("https://example.com"))
|
|
77
|
+
```
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, client: SyncClient | None = None, *, close_client: bool = True) -> None:
|
|
81
|
+
"""Initialize the SyncHttpxTransport.
|
|
82
|
+
:param client: An optional pyreqwest SyncClient instance. If not provided, a default SyncClient will be created.
|
|
83
|
+
:param close_client: Whether to close the provided SyncClient when the transport is closed.
|
|
84
|
+
"""
|
|
85
|
+
self._client: SyncClient = client or SyncClientBuilder().build()
|
|
86
|
+
self._close_client = (client is None) or close_client
|
|
87
|
+
|
|
88
|
+
def close(self) -> None:
|
|
89
|
+
"""Close the underlying pyreqwest SyncClient if needed."""
|
|
90
|
+
if self._close_client:
|
|
91
|
+
self._client.close()
|
|
92
|
+
|
|
93
|
+
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
|
94
|
+
"""BaseTransport implementation to handle httpx request using pyreqwest."""
|
|
95
|
+
req_builder = self._client.request(request.method, str(request.url)).headers(request.headers.multi_items())
|
|
96
|
+
req_builder = self._map_body(req_builder, request)
|
|
97
|
+
req_builder = map_extensions(req_builder, request)
|
|
98
|
+
return self._map_response(req_builder, request)
|
|
99
|
+
|
|
100
|
+
def _map_body(self, builder: SyncRequestBuilder, request: httpx.Request) -> SyncRequestBuilder:
|
|
101
|
+
try:
|
|
102
|
+
return builder.body_bytes(request.content)
|
|
103
|
+
except httpx.RequestNotRead:
|
|
104
|
+
if isinstance(request.stream, httpx.AsyncByteStream):
|
|
105
|
+
err = "Cannot use async stream in sync transport"
|
|
106
|
+
raise TypeError(err) from None
|
|
107
|
+
return builder.body_stream(request.stream)
|
|
108
|
+
|
|
109
|
+
def _map_response(self, builder: SyncRequestBuilder, request: httpx.Request) -> httpx.Response:
|
|
110
|
+
req_exit_stack = ExitStack()
|
|
111
|
+
try:
|
|
112
|
+
response = req_exit_stack.enter_context(builder.build_streamed())
|
|
113
|
+
return build_httpx_response(response, response_stream=SyncResponseStream(response, req_exit_stack))
|
|
114
|
+
except Exception as exc:
|
|
115
|
+
req_exit_stack.close()
|
|
116
|
+
if isinstance(exc, PyreqwestError) and (mapped := map_exception(exc, request)):
|
|
117
|
+
raise mapped from exc
|
|
118
|
+
raise
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ResponseStream(httpx.AsyncByteStream):
|
|
122
|
+
"""httpx AsyncByteStream that wraps a pyreqwest Response body reader."""
|
|
123
|
+
|
|
124
|
+
def __init__(self, response: Response, exit_stack: AsyncExitStack) -> None:
|
|
125
|
+
"""Internally initialized."""
|
|
126
|
+
self._body_reader = response.body_reader
|
|
127
|
+
self._exit_stack = exit_stack
|
|
128
|
+
|
|
129
|
+
async def __aiter__(self) -> AsyncIterator[bytes]:
|
|
130
|
+
"""Asynchronously iterate over the response body in chunks."""
|
|
131
|
+
while (chunk := await self._body_reader.read()) is not None:
|
|
132
|
+
yield bytes(chunk)
|
|
133
|
+
|
|
134
|
+
async def aclose(self) -> None:
|
|
135
|
+
"""Close the response stream."""
|
|
136
|
+
await self._exit_stack.aclose()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SyncResponseStream(httpx.SyncByteStream):
|
|
140
|
+
"""httpx SyncByteStream that wraps a pyreqwest SyncResponse body reader."""
|
|
141
|
+
|
|
142
|
+
def __init__(self, response: SyncResponse, exit_stack: ExitStack) -> None:
|
|
143
|
+
"""Internally initialized."""
|
|
144
|
+
self._body_reader = response.body_reader
|
|
145
|
+
self._exit_stack = exit_stack
|
|
146
|
+
|
|
147
|
+
def __iter__(self) -> Iterator[bytes]:
|
|
148
|
+
"""Iterate over the response body in chunks."""
|
|
149
|
+
while (chunk := self._body_reader.read()) is not None:
|
|
150
|
+
yield bytes(chunk)
|
|
151
|
+
|
|
152
|
+
def close(self) -> None:
|
|
153
|
+
"""Close the response stream."""
|
|
154
|
+
self._exit_stack.close()
|
pyreqwest/pytest_plugin/types.py
CHANGED
|
@@ -14,7 +14,7 @@ try:
|
|
|
14
14
|
Matcher = str | Pattern[str] | DirtyEquals[Any]
|
|
15
15
|
JsonMatcher = DirtyEquals[Any] | Any
|
|
16
16
|
except ImportError:
|
|
17
|
-
Matcher = str | Pattern[str] # type: ignore[misc]
|
|
17
|
+
Matcher = str | Pattern[str] # type: ignore[assignment,misc]
|
|
18
18
|
JsonMatcher = Any # type: ignore[assignment,misc]
|
|
19
19
|
|
|
20
20
|
MethodMatcher = Matcher
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyreqwest
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Programming Language :: Python
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -32,6 +32,8 @@ Project-URL: Source, https://github.com/MarkusSintonen/pyreqwest
|
|
|
32
32
|
</p>
|
|
33
33
|
|
|
34
34
|
---
|
|
35
|
+
[](https://codecov.io/github/markussintonen/pyreqwest)
|
|
36
|
+

|
|
35
37
|
|
|
36
38
|
pyreqwest - Powerful and fast Rust based HTTP client. Built on top of and inspired by [reqwest](https://github.com/seanmonstar/reqwest).
|
|
37
39
|
|
|
@@ -104,3 +106,7 @@ See [docs](https://markussintonen.github.io/pyreqwest/pyreqwest.html)
|
|
|
104
106
|
|
|
105
107
|
See [examples](https://github.com/MarkusSintonen/pyreqwest/tree/main/examples)
|
|
106
108
|
|
|
109
|
+
## Compatibility with other libraries
|
|
110
|
+
|
|
111
|
+
See [compatibility docs](https://markussintonen.github.io/pyreqwest/pyreqwest/compatibility.html)
|
|
112
|
+
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
pyreqwest-0.
|
|
2
|
-
pyreqwest-0.
|
|
3
|
-
pyreqwest-0.
|
|
4
|
-
pyreqwest-0.
|
|
1
|
+
pyreqwest-0.6.0.dist-info/METADATA,sha256=vWepMhQitXcL4ksYIzjgOpIp9OoL-HN8KGiqtIuMxB8,4678
|
|
2
|
+
pyreqwest-0.6.0.dist-info/WHEEL,sha256=dmU0emcWFy7I2vAjCdbFaeE-WAcokqKnhgHxa8mdn3w,107
|
|
3
|
+
pyreqwest-0.6.0.dist-info/entry_points.txt,sha256=x6BM9g8m805tw6VqKvSe_Kgd2qp4Eq3moyIoFnETeoE,61
|
|
4
|
+
pyreqwest-0.6.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
5
5
|
pyreqwest/__init__.py,sha256=AqtJmAtOEQ6kYGY2_Qzw2f_q5K3Q5PN-TpOmIKaeiEM,86
|
|
6
6
|
pyreqwest/__init__.pyi,sha256=Y25n44pyE3vp92MiABKrcK3IWRyQ1JG1rZ4Ufqy2nC0,17
|
|
7
|
-
pyreqwest/_pyreqwest.cpython-311-darwin.so,sha256=
|
|
7
|
+
pyreqwest/_pyreqwest.cpython-311-darwin.so,sha256=SstGxOrAj6EozakIxF4nQoi15vNmqHQoy55fw6SZOGo,6725484
|
|
8
8
|
pyreqwest/bytes/__init__.py,sha256=uzB8cxWbBizu0kyrdXhmnwWKWdwBcVIv4mgEhCR1LMs,465
|
|
9
9
|
pyreqwest/bytes/__init__.pyi,sha256=bttN8uCg7K3gW90FBgPP4vbtNrBCOwqX4v7LAK-T1pU,4569
|
|
10
10
|
pyreqwest/client/__init__.py,sha256=tPfCyh-HJJEepvGMvLSLjKkZySTKsATsnbJziT_P4IM,369
|
|
11
11
|
pyreqwest/client/__init__.pyi,sha256=y9AfLH2Dhw8UOnnia1hk1qVGjAGW43LGT3dwqZfEVm4,14231
|
|
12
12
|
pyreqwest/client/types.py,sha256=CpfplqBGOyvpZNpwbOqal89dBztZlZTYuX-nM1Qrwrg,1475
|
|
13
|
+
pyreqwest/compatibility/__init__.py,sha256=yJ4VD9iMzvSpYDjM6kEM-F2A_avMFCkOo1g5bgD2AGU,102
|
|
14
|
+
pyreqwest/compatibility/httpx/__init__.py,sha256=Gc0jMCgmpSHkqQha_zAzxXjrbRSvS6KUOnHp4Z0UMDc,334
|
|
15
|
+
pyreqwest/compatibility/httpx/_internal.py,sha256=olu1ES7bV8AHUqtEMEmGHlejQCxAS3BYwl_r0GunAew,2220
|
|
16
|
+
pyreqwest/compatibility/httpx/transport.py,sha256=2wuhREEvpWvcrCZcNv0mH_7MbuqwCO0_b9j7TMxkee8,6672
|
|
13
17
|
pyreqwest/cookie/__init__.py,sha256=whcJaTtVHgqH2n_lvGYXYHP7D9BolDU6SvKjuKpewAs,128
|
|
14
18
|
pyreqwest/cookie/__init__.pyi,sha256=YCOI79evRpc7xmSpY6q1MsQMCHSucGDpLmsk0ZqfXN4,6401
|
|
15
19
|
pyreqwest/exceptions/__init__.py,sha256=LWD8I6hFsw0ogHwpobPVcO5tfFRhRg0rPF2Bs64XjuI,5319
|
|
@@ -32,10 +36,10 @@ pyreqwest/pytest_plugin/internal/assert_message.py,sha256=oRkvzpIFw69rOaGTYBFGd8
|
|
|
32
36
|
pyreqwest/pytest_plugin/internal/matcher.py,sha256=aB08rUdI3rLQL2pMxerYAD-S8p1TeFUl36i0G2-tdgs,1221
|
|
33
37
|
pyreqwest/pytest_plugin/internal/plugin.py,sha256=NkneUC5Ui-1ATlrG0q-R4_xdkA_Ou3UEF3t6qAlhS_M,448
|
|
34
38
|
pyreqwest/pytest_plugin/mock.py,sha256=f3Zdy0CAlPc4RVZcSktPU6WfrhAmCtbzZnZyxizizqI,20315
|
|
35
|
-
pyreqwest/pytest_plugin/types.py,sha256=
|
|
39
|
+
pyreqwest/pytest_plugin/types.py,sha256=QyFxjkth7favATSwt3d8EtUBcVjqcUYQ5BxA617T54g,895
|
|
36
40
|
pyreqwest/request/__init__.py,sha256=D5JI5nbfJgBgnGK_2fxCR1bPjGhy-yr1U4PoPY_RtsM,504
|
|
37
41
|
pyreqwest/request/__init__.pyi,sha256=UB9IMIep6xwCvinv7OD9lNTNYLw-kkxdVMEqvVDRFkk,7149
|
|
38
42
|
pyreqwest/response/__init__.py,sha256=u1M-aMKJdS3EQ9NqANnpnZ4S2XeWsM2ncd73Oj7T1_E,373
|
|
39
43
|
pyreqwest/response/__init__.pyi,sha256=8hebHvfuWadg4zi0esEMJikTrXE296DlWHbYGGkLNfQ,5737
|
|
40
44
|
pyreqwest/types.py,sha256=10lGx5uugbo5awMz6nNOcuIsyeG3NIB7DqQHiPbOHLk,643
|
|
41
|
-
pyreqwest-0.
|
|
45
|
+
pyreqwest-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|