pyreqwest 0.5.1__cp314-cp314-macosx_10_12_x86_64.whl → 0.6.0__cp314-cp314-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.
Binary file
@@ -0,0 +1,4 @@
1
+ """Compatibility layers for different HTTP client libraries.
2
+
3
+ See individual modules for details.
4
+ """
@@ -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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyreqwest
3
- Version: 0.5.1
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
+ [![codecov](https://codecov.io/github/markussintonen/pyreqwest/graph/badge.svg?token=OET0CMZYOH)](https://codecov.io/github/markussintonen/pyreqwest)
36
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyreqwest)
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.5.1.dist-info/METADATA,sha256=w1NHKbA5Buar80S3xPfnQ0fyTBVvQ23PmL_dGG1S8Jk,4315
2
- pyreqwest-0.5.1.dist-info/WHEEL,sha256=0p9RWqZ1tTIS4XVGVX-OqQSPs2Wp470cV_pQ0zUt98A,107
3
- pyreqwest-0.5.1.dist-info/entry_points.txt,sha256=x6BM9g8m805tw6VqKvSe_Kgd2qp4Eq3moyIoFnETeoE,61
4
- pyreqwest-0.5.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
1
+ pyreqwest-0.6.0.dist-info/METADATA,sha256=vWepMhQitXcL4ksYIzjgOpIp9OoL-HN8KGiqtIuMxB8,4678
2
+ pyreqwest-0.6.0.dist-info/WHEEL,sha256=0p9RWqZ1tTIS4XVGVX-OqQSPs2Wp470cV_pQ0zUt98A,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-314-darwin.so,sha256=xIE_cACifQRc9EwOCOu8ENCKK8OkIN_ztYPj39OG3gU,6721716
7
+ pyreqwest/_pyreqwest.cpython-314-darwin.so,sha256=NXF4_HUhCzKpuZVmHMGVWgt4hZxEwGex2ZASwgE-Dgk,6717524
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
@@ -38,4 +42,4 @@ pyreqwest/request/__init__.pyi,sha256=UB9IMIep6xwCvinv7OD9lNTNYLw-kkxdVMEqvVDRFk
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.5.1.dist-info/RECORD,,
45
+ pyreqwest-0.6.0.dist-info/RECORD,,