pyreqwest 0.8.0__cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.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/__init__.py +3 -0
- pyreqwest/__init__.pyi +1 -0
- pyreqwest/_pyreqwest.cpython-314-s390x-linux-gnu.so +0 -0
- pyreqwest/bytes/__init__.py +16 -0
- pyreqwest/bytes/__init__.pyi +106 -0
- pyreqwest/client/__init__.py +21 -0
- pyreqwest/client/__init__.pyi +349 -0
- pyreqwest/client/types.py +54 -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/cookie/__init__.py +5 -0
- pyreqwest/cookie/__init__.pyi +174 -0
- pyreqwest/exceptions/__init__.py +193 -0
- pyreqwest/http/__init__.py +19 -0
- pyreqwest/http/__init__.pyi +344 -0
- pyreqwest/logging/__init__.py +7 -0
- pyreqwest/logging/__init__.pyi +4 -0
- pyreqwest/middleware/__init__.py +5 -0
- pyreqwest/middleware/__init__.pyi +12 -0
- pyreqwest/middleware/asgi/__init__.py +5 -0
- pyreqwest/middleware/asgi/asgi.py +168 -0
- pyreqwest/middleware/types.py +26 -0
- pyreqwest/multipart/__init__.py +5 -0
- pyreqwest/multipart/__init__.pyi +75 -0
- pyreqwest/proxy/__init__.py +5 -0
- pyreqwest/proxy/__init__.pyi +47 -0
- pyreqwest/py.typed +0 -0
- pyreqwest/pytest_plugin/__init__.py +8 -0
- pyreqwest/pytest_plugin/internal/__init__.py +0 -0
- pyreqwest/pytest_plugin/internal/assert_eq.py +6 -0
- pyreqwest/pytest_plugin/internal/assert_message.py +123 -0
- pyreqwest/pytest_plugin/internal/matcher.py +34 -0
- pyreqwest/pytest_plugin/internal/plugin.py +15 -0
- pyreqwest/pytest_plugin/mock.py +512 -0
- pyreqwest/pytest_plugin/types.py +26 -0
- pyreqwest/request/__init__.py +29 -0
- pyreqwest/request/__init__.pyi +218 -0
- pyreqwest/response/__init__.py +19 -0
- pyreqwest/response/__init__.pyi +157 -0
- pyreqwest/simple/__init__.py +1 -0
- pyreqwest/simple/request/__init__.py +21 -0
- pyreqwest/simple/request/__init__.pyi +29 -0
- pyreqwest/simple/sync_request/__init__.py +21 -0
- pyreqwest/simple/sync_request/__init__.pyi +29 -0
- pyreqwest/types.py +12 -0
- pyreqwest-0.8.0.dist-info/METADATA +148 -0
- pyreqwest-0.8.0.dist-info/RECORD +52 -0
- pyreqwest-0.8.0.dist-info/WHEEL +5 -0
- pyreqwest-0.8.0.dist-info/entry_points.txt +2 -0
- pyreqwest-0.8.0.dist-info/licenses/LICENSE +201 -0
|
@@ -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()
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""HTTP cookie types backed by Rust's cookie and cookie_store crates."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from typing import Any, Literal, Self, TypeAlias, overload
|
|
6
|
+
|
|
7
|
+
from pyreqwest.http import Url
|
|
8
|
+
|
|
9
|
+
SameSite: TypeAlias = Literal["Strict", "Lax", "None"]
|
|
10
|
+
|
|
11
|
+
class Cookie:
|
|
12
|
+
"""An immutable HTTP cookie. Lightweight Python wrapper around the internal Rust cookie::Cookie type.
|
|
13
|
+
Use `with_*` methods to create modified copies of a Cookie.
|
|
14
|
+
|
|
15
|
+
See also Rust [docs](https://docs.rs/cookie/latest/cookie/struct.Cookie.html) for more details.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, name: str, value: str) -> None:
|
|
19
|
+
"""Create a cookie with the given name and value (no attributes)."""
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def parse(cookie: str) -> "Cookie":
|
|
23
|
+
"""Parses a Cookie from the given HTTP cookie header value string."""
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def parse_encoded(cookie: str) -> "Cookie":
|
|
27
|
+
"""Like parse, but does percent-decoding of keys and values."""
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def split_parse(cookie: str) -> list["Cookie"]:
|
|
31
|
+
"""Parses the HTTP Cookie header, a series of cookie names and value separated by `;`."""
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def split_parse_encoded(cookie: str) -> list["Cookie"]:
|
|
35
|
+
"""Like split_parse, but does percent-decoding of keys and values."""
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def name(self) -> str:
|
|
39
|
+
"""Cookie name."""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def value(self) -> str:
|
|
43
|
+
"""Raw cookie value as set (may contain surrounding whitespace)."""
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def value_trimmed(self) -> str:
|
|
47
|
+
"""Value with surrounding whitespace trimmed."""
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def http_only(self) -> bool:
|
|
51
|
+
"""Whether the HttpOnly attribute is set."""
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def secure(self) -> bool:
|
|
55
|
+
"""Whether the Secure attribute is set."""
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def same_site(self) -> SameSite | None:
|
|
59
|
+
"""SameSite attribute, or None if unspecified."""
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def partitioned(self) -> bool:
|
|
63
|
+
"""Whether the Partitioned attribute is set."""
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def max_age(self) -> timedelta | None:
|
|
67
|
+
"""Max-Age attribute duration, or None if not present."""
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def path(self) -> str | None:
|
|
71
|
+
"""Path attribute that scopes the cookie, or None if not present."""
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def domain(self) -> str | None:
|
|
75
|
+
"""Domain attribute that scopes the cookie, or None if not present."""
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def expires_datetime(self) -> datetime | None:
|
|
79
|
+
"""Absolute expiration time (Expires), or None if not present."""
|
|
80
|
+
|
|
81
|
+
def encode(self) -> str:
|
|
82
|
+
"""Returns cookie string with percent-encoding applied."""
|
|
83
|
+
|
|
84
|
+
def stripped(self) -> str:
|
|
85
|
+
"""Return just the 'name=value' pair."""
|
|
86
|
+
|
|
87
|
+
def with_name(self, name: str) -> Self:
|
|
88
|
+
"""Set name, returning a new Cookie."""
|
|
89
|
+
|
|
90
|
+
def with_value(self, value: str) -> Self:
|
|
91
|
+
"""Set value, returning a new Cookie."""
|
|
92
|
+
|
|
93
|
+
def with_http_only(self, http_only: bool) -> Self:
|
|
94
|
+
"""Set HttpOnly attribute, returning a new Cookie."""
|
|
95
|
+
|
|
96
|
+
def with_secure(self, secure: bool) -> Self:
|
|
97
|
+
"""Set Secure attribute, returning a new Cookie."""
|
|
98
|
+
|
|
99
|
+
def with_same_site(self, same_site: SameSite | None) -> Self:
|
|
100
|
+
"""Set SameSite attribute, returning a new Cookie."""
|
|
101
|
+
|
|
102
|
+
def with_partitioned(self, partitioned: bool) -> Self:
|
|
103
|
+
"""Set Partitioned attribute, returning a new Cookie."""
|
|
104
|
+
|
|
105
|
+
def with_max_age(self, max_age: timedelta | None) -> Self:
|
|
106
|
+
"""Set Max-Age attribute, returning a new Cookie."""
|
|
107
|
+
|
|
108
|
+
def with_path(self, path: str | None) -> Self:
|
|
109
|
+
"""Set Path attribute, returning a new Cookie."""
|
|
110
|
+
|
|
111
|
+
def with_domain(self, domain: str | None) -> Self:
|
|
112
|
+
"""Set Domain attribute, returning a new Cookie."""
|
|
113
|
+
|
|
114
|
+
def with_expires_datetime(self, expires: datetime | None) -> Self:
|
|
115
|
+
"""Set Expires attribute, returning a new Cookie."""
|
|
116
|
+
|
|
117
|
+
def __contains__(self, item: Any) -> bool: ...
|
|
118
|
+
def __copy__(self) -> Self: ...
|
|
119
|
+
def __hash__(self) -> int: ...
|
|
120
|
+
@overload
|
|
121
|
+
def __getitem__(self, index: int) -> str: ...
|
|
122
|
+
@overload
|
|
123
|
+
def __getitem__(self, index: slice) -> Sequence[str]: ...
|
|
124
|
+
def __len__(self) -> int: ...
|
|
125
|
+
def __eq__(self, other: object) -> bool: ...
|
|
126
|
+
def __lt__(self, other: object) -> bool: ...
|
|
127
|
+
def __le__(self, other: object) -> bool: ...
|
|
128
|
+
|
|
129
|
+
class CookieStore:
|
|
130
|
+
"""Thread-safe in-memory cookie store (domain/path aware). Mirrors the behavior of Rust's cookie_store.
|
|
131
|
+
|
|
132
|
+
See also Rust [docs](https://docs.rs/cookie_store/latest/cookie_store/struct.CookieStore.html) for more details.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(self) -> None:
|
|
136
|
+
"""Create an empty cookie store."""
|
|
137
|
+
|
|
138
|
+
def contains(self, domain: str, path: str, name: str) -> bool:
|
|
139
|
+
"""Returns true if the CookieStore contains an unexpired Cookie corresponding to the specified domain, path,
|
|
140
|
+
and name.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def contains_any(self, domain: str, path: str, name: str) -> bool:
|
|
144
|
+
"""Returns true if the CookieStore contains any (even an expired) Cookie corresponding to the specified
|
|
145
|
+
domain, path, and name.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def get(self, domain: str, path: str, name: str) -> Cookie | None:
|
|
149
|
+
"""Returns a reference to the unexpired Cookie corresponding to the specified domain, path, and name."""
|
|
150
|
+
|
|
151
|
+
def get_any(self, domain: str, path: str, name: str) -> Cookie | None:
|
|
152
|
+
"""Returns a reference to the (possibly expired) Cookie corresponding to the specified domain, path, and
|
|
153
|
+
name.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def remove(self, domain: str, path: str, name: str) -> Cookie | None:
|
|
157
|
+
"""Removes a Cookie from the store, returning the Cookie if it was in the store."""
|
|
158
|
+
|
|
159
|
+
def matches(self, url: Url | str) -> list[Cookie]:
|
|
160
|
+
"""Returns a collection of references to unexpired cookies that path- and domain-match request_url, as well as
|
|
161
|
+
having HttpOnly and Secure attributes compatible with the request_url.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def insert(self, cookie: Cookie | str, request_url: Url | str) -> None:
|
|
165
|
+
"""Insert a cookie as if set by a response for request_url."""
|
|
166
|
+
|
|
167
|
+
def clear(self) -> None:
|
|
168
|
+
"""Remove all cookies from the store."""
|
|
169
|
+
|
|
170
|
+
def get_all_unexpired(self) -> list[Cookie]:
|
|
171
|
+
"""Return all unexpired cookies currently stored."""
|
|
172
|
+
|
|
173
|
+
def get_all_any(self) -> list[Cookie]:
|
|
174
|
+
"""Return all cookies in the store, including expired ones."""
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Exception classes."""
|
|
2
|
+
|
|
3
|
+
from json import JSONDecodeError as JSONDecodeError_
|
|
4
|
+
from typing import Any, Generic, TypedDict, TypeVar
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Cause(TypedDict):
|
|
8
|
+
"""A cause of an error."""
|
|
9
|
+
|
|
10
|
+
message: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CauseErrorDetails(TypedDict):
|
|
14
|
+
"""Details for errors that may have causes."""
|
|
15
|
+
|
|
16
|
+
causes: list[Cause] | None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StatusErrorDetails(TypedDict):
|
|
20
|
+
"""Details for errors that have an associated HTTP status code."""
|
|
21
|
+
|
|
22
|
+
status: int
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
T = TypeVar("T", bound=CauseErrorDetails | StatusErrorDetails)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PyreqwestError(Exception):
|
|
29
|
+
"""Base class for all pyreqwest errors."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, message: str, *args: Any) -> None:
|
|
32
|
+
"""Internally initialized."""
|
|
33
|
+
assert isinstance(message, str)
|
|
34
|
+
Exception.__init__(self, message, *args)
|
|
35
|
+
self.message = message
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DetailedPyreqwestError(PyreqwestError, Generic[T]):
|
|
39
|
+
"""Base class for all pyreqwest errors with details.
|
|
40
|
+
|
|
41
|
+
Details may be available in `details`.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, message: str, details: T) -> None:
|
|
45
|
+
"""Internally initialized."""
|
|
46
|
+
assert isinstance(details, dict)
|
|
47
|
+
PyreqwestError.__init__(self, message, details)
|
|
48
|
+
self.details = details
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RequestError(DetailedPyreqwestError[T], Generic[T]):
|
|
52
|
+
"""Error while processing a request.
|
|
53
|
+
|
|
54
|
+
Details may be available in `details`.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StatusError(RequestError[StatusErrorDetails]):
|
|
59
|
+
"""Error due to HTTP 4xx or 5xx status code. Raised when `error_for_status` is enabled.
|
|
60
|
+
|
|
61
|
+
The status code is available in `details["status"]`.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class RedirectError(RequestError[CauseErrorDetails]):
|
|
66
|
+
"""Error due to too many redirects. Raised when `max_redirects` is exceeded.
|
|
67
|
+
|
|
68
|
+
Cause details may be available in `details["causes"]`.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class DecodeError(RequestError[CauseErrorDetails]):
|
|
73
|
+
"""Error while decoding the response.
|
|
74
|
+
|
|
75
|
+
Cause details may be available in `details["causes"]`.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class BodyDecodeError(DecodeError):
|
|
80
|
+
"""Error while decoding the request or response body.
|
|
81
|
+
|
|
82
|
+
Cause details may be available in `details["causes"]`.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class JSONDecodeError(BodyDecodeError, JSONDecodeError_):
|
|
87
|
+
"""Error while decoding the response body as JSON.
|
|
88
|
+
|
|
89
|
+
This corresponds to Python's built-in `json.JSONDecodeError`. With the difference that `pos` and `colno` are byte
|
|
90
|
+
offsets instead of UTF8 char offsets. This difference is for efficient error handling (avoiding UTF8 conversions).
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self, message: str, details: dict[str, Any]) -> None:
|
|
94
|
+
"""Internally initialized."""
|
|
95
|
+
assert isinstance(details, dict)
|
|
96
|
+
assert isinstance(details["doc"], str) and isinstance(details["pos"], int)
|
|
97
|
+
JSONDecodeError_.__init__(self, message, details["doc"], details["pos"])
|
|
98
|
+
BodyDecodeError.__init__(self, message, {"causes": details["causes"]})
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class TransportError(RequestError[CauseErrorDetails]):
|
|
102
|
+
"""Error while processing the transport layer.
|
|
103
|
+
|
|
104
|
+
Cause details may be available in `details["causes"]`.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class RequestTimeoutError(TransportError, TimeoutError):
|
|
109
|
+
"""Error due to a timeout.
|
|
110
|
+
|
|
111
|
+
This indicates that the timeout configured for the request was reached.
|
|
112
|
+
Cause details may be available in `details["causes"]`.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class NetworkError(TransportError):
|
|
117
|
+
"""Error due to a network failure.
|
|
118
|
+
|
|
119
|
+
This indicates that the request could not be completed due to a network failure.
|
|
120
|
+
Cause details may be available in `details["causes"]`.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ConnectTimeoutError(RequestTimeoutError):
|
|
125
|
+
"""Timeout while connecting.
|
|
126
|
+
|
|
127
|
+
Cause details may be available in `details["causes"]`.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ReadTimeoutError(RequestTimeoutError):
|
|
132
|
+
"""Timeout while reading body.
|
|
133
|
+
|
|
134
|
+
Cause details may be available in `details["causes"]`.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class WriteTimeoutError(RequestTimeoutError):
|
|
139
|
+
"""Timeout while sending body.
|
|
140
|
+
|
|
141
|
+
Cause details may be available in `details["causes"]`.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class PoolTimeoutError(RequestTimeoutError):
|
|
146
|
+
"""Timeout while acquiring a connection from the pool.
|
|
147
|
+
|
|
148
|
+
Cause details may be available in `details["causes"]`.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ConnectError(NetworkError):
|
|
153
|
+
"""Network error while connecting.
|
|
154
|
+
|
|
155
|
+
Cause details may be available in `details["causes"]`.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ReadError(NetworkError):
|
|
160
|
+
"""Network error while reading body.
|
|
161
|
+
|
|
162
|
+
Cause details may be available in `details["causes"]`.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class WriteError(NetworkError):
|
|
167
|
+
"""Network error while sending body.
|
|
168
|
+
|
|
169
|
+
Cause details may be available in `details["causes"]`.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ClientClosedError(RequestError[CauseErrorDetails]):
|
|
174
|
+
"""Error due to user closing the client while request was being processed.
|
|
175
|
+
|
|
176
|
+
Cause details may be available in `details["causes"]`.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class BuilderError(DetailedPyreqwestError[CauseErrorDetails], ValueError):
|
|
181
|
+
"""Error while building a request.
|
|
182
|
+
|
|
183
|
+
Cause details may be available in `details["causes"]`.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class RequestPanicError(RequestError[CauseErrorDetails]):
|
|
188
|
+
"""Error due to a panic in the request processing.
|
|
189
|
+
|
|
190
|
+
This indicates a bug in pyreqwest or one of its dependencies.
|
|
191
|
+
Also, might be raised due to incorrect ProxyBuilder.custom implementation (limitation in reqwest error handling).
|
|
192
|
+
Cause details may be available in `details["causes"]`.
|
|
193
|
+
"""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""HTTP utils classes and types."""
|
|
2
|
+
|
|
3
|
+
from pyreqwest._pyreqwest.http import (
|
|
4
|
+
HeaderMap,
|
|
5
|
+
HeaderMapItemsView,
|
|
6
|
+
HeaderMapKeysView,
|
|
7
|
+
HeaderMapValuesView,
|
|
8
|
+
Mime,
|
|
9
|
+
Url,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Url",
|
|
14
|
+
"HeaderMap",
|
|
15
|
+
"Mime",
|
|
16
|
+
"HeaderMapItemsView",
|
|
17
|
+
"HeaderMapKeysView",
|
|
18
|
+
"HeaderMapValuesView",
|
|
19
|
+
]
|