RESTFly 2.0.3__py3-none-any.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.
- restfly/__init__.py +25 -0
- restfly/_async.py +1570 -0
- restfly/_base.py +340 -0
- restfly/_errors.py +213 -0
- restfly/_iterator.py +302 -0
- restfly/_models.py +98 -0
- restfly/_sync.py +1565 -0
- restfly/_utils.py +105 -0
- restfly/_version.py +1 -0
- restfly/types.py +77 -0
- restfly-2.0.3.dist-info/METADATA +48 -0
- restfly-2.0.3.dist-info/RECORD +15 -0
- restfly-2.0.3.dist-info/WHEEL +5 -0
- restfly-2.0.3.dist-info/licenses/LICENSE +19 -0
- restfly-2.0.3.dist-info/top_level.txt +1 -0
restfly/_base.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import platform
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from ssl import SSLContext
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
from pydantic_xml import BaseXmlModel
|
|
12
|
+
|
|
13
|
+
from ._errors import APIError, ErrorStatus, build_error_map
|
|
14
|
+
from ._utils import assign_annotations
|
|
15
|
+
from ._version import version as RESTFLY_VERSION
|
|
16
|
+
from .types import (
|
|
17
|
+
DEFAULT_LIMITS,
|
|
18
|
+
DEFAULT_MAX_REDIRECTS,
|
|
19
|
+
DEFAULT_TIMEOUT_CONFIG,
|
|
20
|
+
HTTPX_VERSION,
|
|
21
|
+
USE_CLIENT_DEFAULT,
|
|
22
|
+
AsyncBaseTransport,
|
|
23
|
+
AsyncClient,
|
|
24
|
+
AuthTypes,
|
|
25
|
+
BaseTransport,
|
|
26
|
+
CertTypes,
|
|
27
|
+
Client,
|
|
28
|
+
CookieTypes,
|
|
29
|
+
EventHook,
|
|
30
|
+
HeaderTypes,
|
|
31
|
+
HTTPMethods,
|
|
32
|
+
Limits,
|
|
33
|
+
Model,
|
|
34
|
+
ProxyTypes,
|
|
35
|
+
QueryParamTypes,
|
|
36
|
+
RequestContent,
|
|
37
|
+
RequestData,
|
|
38
|
+
RequestExtensions,
|
|
39
|
+
RequestFiles,
|
|
40
|
+
TimeoutTypes,
|
|
41
|
+
UseClientDefault,
|
|
42
|
+
XMLModel,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class APIClientBase:
|
|
47
|
+
__client_class__: type[Client | AsyncClient]
|
|
48
|
+
__endpoint_class__: type[APIBaseEndpoint]
|
|
49
|
+
|
|
50
|
+
_base_url: str = ""
|
|
51
|
+
""" The base URL fragment to prefix all API calls with. """
|
|
52
|
+
|
|
53
|
+
_client: Client | AsyncClient
|
|
54
|
+
""" HTTPX Client Session object """
|
|
55
|
+
|
|
56
|
+
_json_load_kwargs: dict[str, Any]
|
|
57
|
+
""" Client-level Pydantic BaseModel.model_validate kwargs """
|
|
58
|
+
|
|
59
|
+
_json_dump_kwargs: dict[str, Any]
|
|
60
|
+
""" Client-level Pydantic BaseModel.model_dump kwargs """
|
|
61
|
+
|
|
62
|
+
_xml_load_kwargs: dict[str, Any]
|
|
63
|
+
""" Client-level Pydantic-XML BaseXmlModel.from_xml kwargs """
|
|
64
|
+
|
|
65
|
+
_xml_dump_kwargs: dict[str, Any]
|
|
66
|
+
""" Client-level Pydantic-XML BaseXmlModel.to_xml kwargs """
|
|
67
|
+
|
|
68
|
+
_lib_name: str = "RESTFly"
|
|
69
|
+
""" Library name """
|
|
70
|
+
|
|
71
|
+
_lib_version: str = "2"
|
|
72
|
+
""" Library version """
|
|
73
|
+
|
|
74
|
+
_retry_max: int = 5
|
|
75
|
+
""" Maximum number of retries to attempt before giving up. """
|
|
76
|
+
|
|
77
|
+
_logger: logging.Logger
|
|
78
|
+
""" Logger for the client """
|
|
79
|
+
|
|
80
|
+
_error_class: type[APIError] = APIError
|
|
81
|
+
""" API Exception Class to use for all API Errors/Exceptions """
|
|
82
|
+
|
|
83
|
+
__error_map__: dict[int, ErrorStatus] | None = None
|
|
84
|
+
""" The default built-in error map. If None the library default is used. """
|
|
85
|
+
|
|
86
|
+
_error_map: defaultdict[int, ErrorStatus]
|
|
87
|
+
"""
|
|
88
|
+
The error map determining how to handle non-OK status codes. Represented by the
|
|
89
|
+
integer status code along with an ErrorStatus data-class object detailing how to
|
|
90
|
+
handle the response. Any overloads to the base error map should be provided here
|
|
91
|
+
and this attribute will be then be replaced at initialization with the merging of
|
|
92
|
+
the default map, this attribute, and anything passed to the constructor. The
|
|
93
|
+
resulting error map will be used during operation of the APIClass object.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
auth: AuthTypes | None = None,
|
|
100
|
+
params: QueryParamTypes | None = None,
|
|
101
|
+
headers: HeaderTypes | None = None,
|
|
102
|
+
cookies: CookieTypes | None = None,
|
|
103
|
+
verify: SSLContext | str | bool = True,
|
|
104
|
+
cert: CertTypes | None = None,
|
|
105
|
+
http1: bool = True,
|
|
106
|
+
http2: bool = False,
|
|
107
|
+
proxy: ProxyTypes | None = None,
|
|
108
|
+
mounts: (dict[str, Any]) | None = None,
|
|
109
|
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
|
110
|
+
follow_redirects: bool = False,
|
|
111
|
+
limits: Limits = DEFAULT_LIMITS,
|
|
112
|
+
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
|
113
|
+
event_hooks: (dict[str, list[EventHook]]) | None = None,
|
|
114
|
+
base_url: str | None = None,
|
|
115
|
+
transport: AsyncBaseTransport | BaseTransport | None = None,
|
|
116
|
+
trust_env: bool = True,
|
|
117
|
+
default_encoding: str | Callable[[bytes], str] = "utf-8",
|
|
118
|
+
vendor: str = "unknown",
|
|
119
|
+
product: str = "unknown",
|
|
120
|
+
build: str = "unknown",
|
|
121
|
+
retry_max: int = 5,
|
|
122
|
+
json_load_kwargs: dict[str, Any] | None = None,
|
|
123
|
+
json_dump_kwargs: dict[str, Any] | None = None,
|
|
124
|
+
xml_load_kwargs: dict[str, Any] | None = None,
|
|
125
|
+
xml_dump_kwargs: dict[str, Any] | None = None,
|
|
126
|
+
error_map: dict[int, ErrorStatus] | None = None,
|
|
127
|
+
error_class: type[APIError] | None = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
# Initialize mutables.
|
|
130
|
+
headers = {} if headers is None else headers
|
|
131
|
+
error_map = {} if error_map is None else error_map
|
|
132
|
+
error_class = self._error_class if error_class is None else error_class
|
|
133
|
+
|
|
134
|
+
# Initialize the private attributes for the client object.
|
|
135
|
+
self._base_url = base_url if base_url else self._base_url
|
|
136
|
+
self._logger = logging.getLogger(__name__)
|
|
137
|
+
self._retry_max = retry_max if retry_max else self._retry_max
|
|
138
|
+
self._json_load_kwargs = (
|
|
139
|
+
json_load_kwargs
|
|
140
|
+
if json_load_kwargs
|
|
141
|
+
else getattr(self, "_json_load_kwargs", {})
|
|
142
|
+
)
|
|
143
|
+
self._json_dump_kwargs = (
|
|
144
|
+
json_dump_kwargs
|
|
145
|
+
if json_dump_kwargs
|
|
146
|
+
else getattr(self, "_json_dump_kwargs", {})
|
|
147
|
+
)
|
|
148
|
+
self._xml_load_kwargs = (
|
|
149
|
+
xml_load_kwargs
|
|
150
|
+
if xml_load_kwargs
|
|
151
|
+
else getattr(self, "_xml_load_kwargs", {})
|
|
152
|
+
)
|
|
153
|
+
self._xml_dump_kwargs = (
|
|
154
|
+
xml_dump_kwargs
|
|
155
|
+
if xml_dump_kwargs
|
|
156
|
+
else getattr(self, "_xml_dump_kwargs", {})
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Construct the error map default dict using the built-in error_map as well as
|
|
160
|
+
# the over loadable _error_map extension and then lastly update with any
|
|
161
|
+
# error_map provided at initialization. Lastly store the resulting defaultdict
|
|
162
|
+
# over the _error_map private attribute.
|
|
163
|
+
self._error_map = build_error_map(
|
|
164
|
+
base_map=self.__error_map__, overloads=error_map, error_class=error_class
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# If we had received a pydantic model for the client params, then we will first
|
|
168
|
+
# coerce them into a python dictionary.
|
|
169
|
+
if isinstance(params, BaseModel):
|
|
170
|
+
params = params.model_dump(mode="json", exclude_none=True)
|
|
171
|
+
|
|
172
|
+
# Instantiate the HTTPX Client with the arguments we have.
|
|
173
|
+
self._client = self.__client_class__(
|
|
174
|
+
auth=auth,
|
|
175
|
+
params=params,
|
|
176
|
+
headers=headers,
|
|
177
|
+
cookies=cookies,
|
|
178
|
+
verify=verify,
|
|
179
|
+
cert=cert,
|
|
180
|
+
http1=http1,
|
|
181
|
+
http2=http2,
|
|
182
|
+
proxy=proxy,
|
|
183
|
+
mounts=mounts,
|
|
184
|
+
timeout=timeout,
|
|
185
|
+
follow_redirects=follow_redirects,
|
|
186
|
+
limits=limits,
|
|
187
|
+
max_redirects=max_redirects,
|
|
188
|
+
event_hooks=event_hooks,
|
|
189
|
+
base_url=self._base_url,
|
|
190
|
+
transport=transport, # type: ignore[arg-type] #ty: ignore[invalid-argument-type]
|
|
191
|
+
trust_env=trust_env,
|
|
192
|
+
default_encoding=default_encoding,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Update the client with the User-Agent header.
|
|
196
|
+
user_agent = (
|
|
197
|
+
f"Integration/1.0 ({vendor}; {product}; Build/{build}) "
|
|
198
|
+
f"{self._lib_name}/{self._lib_version} "
|
|
199
|
+
f"(RESTFly/{RESTFLY_VERSION}; "
|
|
200
|
+
f"HTTPX/{self.__client_class__.__name__}-{HTTPX_VERSION}; "
|
|
201
|
+
f"Python/{platform.python_version()}; "
|
|
202
|
+
f"{platform.system()}/{platform.machine()})"
|
|
203
|
+
)
|
|
204
|
+
self._client.headers.update({"User-Agent": user_agent})
|
|
205
|
+
|
|
206
|
+
# Lastly, we will run the annotation assignment build-in to make sure that the
|
|
207
|
+
# public endpoints are properly grafted to the client class.
|
|
208
|
+
self.__assign_annotations__()
|
|
209
|
+
|
|
210
|
+
def _request_pre_process(
|
|
211
|
+
self,
|
|
212
|
+
method: HTTPMethods,
|
|
213
|
+
url: str,
|
|
214
|
+
params: QueryParamTypes | None = None,
|
|
215
|
+
content: RequestContent | None = None,
|
|
216
|
+
data: RequestData | None = None,
|
|
217
|
+
files: RequestFiles | None = None,
|
|
218
|
+
json: Model | object | None = None,
|
|
219
|
+
xml: XMLModel | str | bytes | None = None,
|
|
220
|
+
headers: dict[str, str] | None = None,
|
|
221
|
+
request_model_kwargs: dict[str, Any] | None = None,
|
|
222
|
+
cookies: CookieTypes | None = None,
|
|
223
|
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
|
224
|
+
extensions: RequestExtensions | None = None,
|
|
225
|
+
) -> dict[str, Any]:
|
|
226
|
+
"""
|
|
227
|
+
Performs preflight pre-processing of the request in preparation to be sent to
|
|
228
|
+
HTTPX. Most notably this method is what handles any potential model marshalling
|
|
229
|
+
into the request format. The output is passed as the kwargs into HTTPXs
|
|
230
|
+
request builder.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
method: The HTTP Method.
|
|
234
|
+
url: The HTTP url (this can be either a partial or a full URL).
|
|
235
|
+
params: Query parameters.
|
|
236
|
+
content: Raw request content.
|
|
237
|
+
data: The HTTP request body.
|
|
238
|
+
files: Any files to upload with the request.
|
|
239
|
+
json: Overrides the content and data attributes with JSON-formatted data.
|
|
240
|
+
xml: Overrides the content and data attributes with XML-formatted data.
|
|
241
|
+
headers: Any additional headers to pass into the request.
|
|
242
|
+
request_model_kwargs:
|
|
243
|
+
The pydantic/pydantic-xml kwargs to be passed to the model as part of
|
|
244
|
+
marshalling the data into the expected format.
|
|
245
|
+
cookies: The cookies to pass to the request.
|
|
246
|
+
timeout: The HTTP request timeout.
|
|
247
|
+
extensions: Any request extensions passed as part of the request.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
The kwargs dictionary to be passed to the request builder.
|
|
251
|
+
"""
|
|
252
|
+
# Initialize mutables
|
|
253
|
+
headers = {} if headers is None else headers
|
|
254
|
+
request_model_kwargs = (
|
|
255
|
+
{} if request_model_kwargs is None else request_model_kwargs
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# The query parameters must always be transformed into a dictionary to be
|
|
259
|
+
# properly passed to HTTPX
|
|
260
|
+
if isinstance(params, BaseModel):
|
|
261
|
+
params = params.model_dump(mode="json", **self._json_dump_kwargs)
|
|
262
|
+
|
|
263
|
+
# If the XML model actually is Pydantic-XML model, then we will want to marshal
|
|
264
|
+
# it into bytes and set the content type to XML.
|
|
265
|
+
if isinstance(xml, BaseXmlModel):
|
|
266
|
+
xml_kwargs: dict[str, Any] = {}
|
|
267
|
+
for updates in (
|
|
268
|
+
getattr(self, "_xml_dump_kwargs", {}),
|
|
269
|
+
request_model_kwargs,
|
|
270
|
+
):
|
|
271
|
+
xml_kwargs.update(updates)
|
|
272
|
+
content = xml.to_xml(**xml_kwargs)
|
|
273
|
+
headers["Content-Type"] = "application/xml"
|
|
274
|
+
|
|
275
|
+
# If the xml attribute is already a string or bytes object, then simply set
|
|
276
|
+
# the content type to XML.
|
|
277
|
+
elif isinstance(xml, (str, bytes)):
|
|
278
|
+
content = xml
|
|
279
|
+
headers["Content-Type"] = "application/xml"
|
|
280
|
+
|
|
281
|
+
# If the json attribute is actually a Pydantic model, then we will want to
|
|
282
|
+
# marshal the data using Pydantics model dump and set the content type to JSON
|
|
283
|
+
elif isinstance(json, BaseModel):
|
|
284
|
+
pydantic_kwargs: dict[str, Any] = {}
|
|
285
|
+
for updates in (
|
|
286
|
+
getattr(self, "_json_dump_kwargs", {}),
|
|
287
|
+
request_model_kwargs,
|
|
288
|
+
):
|
|
289
|
+
pydantic_kwargs.update(updates)
|
|
290
|
+
content = json.model_dump_json(**pydantic_kwargs)
|
|
291
|
+
headers["Content-Type"] = "application/json"
|
|
292
|
+
json = None
|
|
293
|
+
|
|
294
|
+
# Return the kwargs to the caller.
|
|
295
|
+
return {
|
|
296
|
+
"method": method,
|
|
297
|
+
"url": url,
|
|
298
|
+
"params": params,
|
|
299
|
+
"content": content,
|
|
300
|
+
"data": data,
|
|
301
|
+
"files": files,
|
|
302
|
+
"json": json,
|
|
303
|
+
"headers": headers,
|
|
304
|
+
"cookies": cookies,
|
|
305
|
+
"timeout": timeout,
|
|
306
|
+
"extensions": extensions,
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
def __assign_annotations__(self) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Handles Annotation assignment for API Endpoints.
|
|
312
|
+
"""
|
|
313
|
+
assign_annotations(self, self.__endpoint_class__)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class APIBaseEndpoint:
|
|
317
|
+
_path: str | None = None
|
|
318
|
+
""" Path fragment to append to the base url already stored within the client. """
|
|
319
|
+
|
|
320
|
+
_logger: logging.Logger
|
|
321
|
+
"""
|
|
322
|
+
The same logging handler that the client has, it also exists here for convenience.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
_client: APIClientBase
|
|
326
|
+
"""
|
|
327
|
+
The client object that this endpoint is associated with. Provided here in order to
|
|
328
|
+
interact with other endpoints that may be grafted to the client.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
def __init__(self, client: APIClientBase | APIBaseEndpoint) -> None:
|
|
332
|
+
match client:
|
|
333
|
+
case APIClientBase():
|
|
334
|
+
self._client = client
|
|
335
|
+
case APIBaseEndpoint():
|
|
336
|
+
self._client = client._client
|
|
337
|
+
case _:
|
|
338
|
+
raise TypeError(f"Client {client} is not a valid client type.")
|
|
339
|
+
self._logger = client._logger
|
|
340
|
+
assign_annotations(self, self._client.__endpoint_class__)
|
restfly/_errors.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error handling classes and data-classes.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from dataclasses import dataclass, replace
|
|
8
|
+
|
|
9
|
+
from httpx import Response
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RetryError(Exception):
|
|
14
|
+
"""
|
|
15
|
+
RetryError is thrown when too many retries have been made to the endpoint and we
|
|
16
|
+
have been forced to stop attempting to call the API endpoint.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, url: str, method: str, attempts: int):
|
|
20
|
+
super().__init__(f"Too many attempts ({attempts}) to {method} {url}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class APIError(Exception):
|
|
24
|
+
"""
|
|
25
|
+
The Base API Error class to be thrown in the event that a non-OK status code is
|
|
26
|
+
returned.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
status_code: int
|
|
30
|
+
""" Status code of the HTTP Response """
|
|
31
|
+
|
|
32
|
+
response: Response
|
|
33
|
+
""" HTTPX Response object of the exception """
|
|
34
|
+
|
|
35
|
+
obj: BaseModel | None
|
|
36
|
+
""" Coerced data obj of the response (if any) """
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
response: Response,
|
|
41
|
+
template: str,
|
|
42
|
+
obj: BaseModel | None = None,
|
|
43
|
+
):
|
|
44
|
+
self.status_code = response.status_code
|
|
45
|
+
self.obj = obj
|
|
46
|
+
self.response = response
|
|
47
|
+
msg = template.format(response=response, request=response.request)
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
logger.warning(msg)
|
|
50
|
+
super().__init__(msg)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class ErrorStatus:
|
|
55
|
+
"""
|
|
56
|
+
Defines the attributes associated to each non-OK status code.
|
|
57
|
+
|
|
58
|
+
Parameters:
|
|
59
|
+
retry:
|
|
60
|
+
Should the request be retried?
|
|
61
|
+
template:
|
|
62
|
+
The log/exception to be sent. This string template will accept both the
|
|
63
|
+
request and response objects.
|
|
64
|
+
model:
|
|
65
|
+
The Pydantic model (if any) to coerce the response body into.
|
|
66
|
+
exception:
|
|
67
|
+
The Exception class to throw.
|
|
68
|
+
backoff:
|
|
69
|
+
How many seconds to wait before retrying? This field will be overloaded
|
|
70
|
+
if the client detects a Retry-After header and will use the response
|
|
71
|
+
value instead of this if it exists.
|
|
72
|
+
jitter:
|
|
73
|
+
If this value is set to a non-zero value, then when calculating the amount
|
|
74
|
+
of time to wait, the client will randomly select a value between 0 and this
|
|
75
|
+
value and append it to the wait before retying. Normally we will want this
|
|
76
|
+
set to something in order to ensure that we stagger retries to the API to
|
|
77
|
+
prevent overwhelming it.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
retry: bool = False
|
|
81
|
+
template: str = (
|
|
82
|
+
r"[{response.status_code}] Response from {request.method} {request.url}"
|
|
83
|
+
)
|
|
84
|
+
model: type[BaseModel] | None = None
|
|
85
|
+
exception: type[APIError] = APIError
|
|
86
|
+
backoff: float = 1.0
|
|
87
|
+
jitter: float = 0.5
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def build_error_map(
|
|
91
|
+
base_map: dict[int, ErrorStatus] | None = None,
|
|
92
|
+
overloads: dict[int, ErrorStatus] | None = None,
|
|
93
|
+
error_class: type[APIError] | None = None,
|
|
94
|
+
) -> defaultdict[int, ErrorStatus]:
|
|
95
|
+
"""
|
|
96
|
+
Error map builder.
|
|
97
|
+
"""
|
|
98
|
+
if base_map is None:
|
|
99
|
+
base_map = ERROR_MAP
|
|
100
|
+
if overloads is None:
|
|
101
|
+
overloads = {}
|
|
102
|
+
if error_class is None:
|
|
103
|
+
error_class = APIError
|
|
104
|
+
|
|
105
|
+
def error_status() -> ErrorStatus:
|
|
106
|
+
return ErrorStatus(exception=error_class if error_class else APIError)
|
|
107
|
+
|
|
108
|
+
error_map = defaultdict(
|
|
109
|
+
error_status,
|
|
110
|
+
{k: replace(v, exception=error_class) for k, v in base_map.items()},
|
|
111
|
+
)
|
|
112
|
+
error_map.update(
|
|
113
|
+
{k: replace(v, exception=error_class) for k, v in overloads.items()}
|
|
114
|
+
)
|
|
115
|
+
return error_map
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
ERROR_MAP = {
|
|
119
|
+
400: ErrorStatus(template=r"[400] Bad Request {request.method} {request.url}"),
|
|
120
|
+
401: ErrorStatus(template=r"[401] Unauthorized {request.method} {request.url}"),
|
|
121
|
+
402: ErrorStatus(template=r"[402] Payment Required {request.method} {request.url}"),
|
|
122
|
+
403: ErrorStatus(template=r"[403] Access Forbidden {request.method} {request.url}"),
|
|
123
|
+
404: ErrorStatus(template=r"[404] Not Found {request.method} {request.url}"),
|
|
124
|
+
405: ErrorStatus(
|
|
125
|
+
template=r"[405] Method Not Allowed {request.method} {request.url}"
|
|
126
|
+
),
|
|
127
|
+
406: ErrorStatus(template=r"[406] Not Acceptable {request.method} {request.url}"),
|
|
128
|
+
407: ErrorStatus(
|
|
129
|
+
template=r"[407] Proxy Auth Required {request.method} {request.url}"
|
|
130
|
+
),
|
|
131
|
+
408: ErrorStatus(
|
|
132
|
+
template=r"[408] Request Timed Out {request.method} {request.url}"
|
|
133
|
+
),
|
|
134
|
+
409: ErrorStatus(template=r"[409] Request Conflict {request.method} {request.url}"),
|
|
135
|
+
410: ErrorStatus(template=r"[410] Gone {request.method} {request.url}"),
|
|
136
|
+
411: ErrorStatus(template=r"[411] Length Required {request.method} {request.url}"),
|
|
137
|
+
412: ErrorStatus(
|
|
138
|
+
template=r"[412] Precondition Failed {request.method} {request.url}"
|
|
139
|
+
),
|
|
140
|
+
413: ErrorStatus(
|
|
141
|
+
template=r"[413] Content Too Large {request.method} {request.url}"
|
|
142
|
+
),
|
|
143
|
+
414: ErrorStatus(template=r"[414] URI Too Long {request.method} {request.url}"),
|
|
144
|
+
415: ErrorStatus(
|
|
145
|
+
template=r"[415] Unsupported Media Type {request.method} {request.url}"
|
|
146
|
+
),
|
|
147
|
+
416: ErrorStatus(
|
|
148
|
+
template=r"[416] Range Not Satisfiable {request.method} {request.url}"
|
|
149
|
+
),
|
|
150
|
+
417: ErrorStatus(
|
|
151
|
+
template=r"[417] Expectation Failed {request.method} {request.url}"
|
|
152
|
+
),
|
|
153
|
+
418: ErrorStatus(template=r"[418] I'm a Teapot {request.method} {request.url}"),
|
|
154
|
+
421: ErrorStatus(
|
|
155
|
+
template=r"[421] Misdirected Request {request.method} {request.url}"
|
|
156
|
+
),
|
|
157
|
+
422: ErrorStatus(
|
|
158
|
+
template=r"[422] Unprocessable Content {request.method} {request.url}"
|
|
159
|
+
),
|
|
160
|
+
423: ErrorStatus(template=r"[423] Locked {request.method} {request.url}"),
|
|
161
|
+
424: ErrorStatus(
|
|
162
|
+
template=r"[424] Failed Dependency {request.method} {request.url}"
|
|
163
|
+
),
|
|
164
|
+
425: ErrorStatus(template=r"[425] Too Early {request.method} {request.url}"),
|
|
165
|
+
426: ErrorStatus(template=r"[426] Upgrade Required {request.method} {request.url}"),
|
|
166
|
+
428: ErrorStatus(
|
|
167
|
+
template=r"[428] Precondition Required {request.method} {request.url}"
|
|
168
|
+
),
|
|
169
|
+
429: ErrorStatus(
|
|
170
|
+
retry=True,
|
|
171
|
+
template=r"[429] Too Many Requests to {request.method} {request.url}",
|
|
172
|
+
backoff=30,
|
|
173
|
+
),
|
|
174
|
+
431: ErrorStatus(
|
|
175
|
+
template=r"[431] Request Header Fields Too Large {request.method} {request.url}"
|
|
176
|
+
),
|
|
177
|
+
451: ErrorStatus(
|
|
178
|
+
template=r"[451] Unavailable For Legal Reasons {request.method} {request.url}"
|
|
179
|
+
),
|
|
180
|
+
500: ErrorStatus(
|
|
181
|
+
template=r"[500] Internal Server Error {request.method} {request.url}"
|
|
182
|
+
),
|
|
183
|
+
501: ErrorStatus(template=r"[501] Not Implemented {request.method} {request.url}"),
|
|
184
|
+
502: ErrorStatus(
|
|
185
|
+
retry=True,
|
|
186
|
+
template=r"[502] Bad Gateway Response {request.method} {request.url}",
|
|
187
|
+
backoff=10,
|
|
188
|
+
),
|
|
189
|
+
503: ErrorStatus(
|
|
190
|
+
retry=True,
|
|
191
|
+
template=r"[503] Service Unavailable {request.method} {request.url}",
|
|
192
|
+
backoff=10,
|
|
193
|
+
),
|
|
194
|
+
504: ErrorStatus(
|
|
195
|
+
retry=True,
|
|
196
|
+
template=r"[504] Gateway Timeout {request.method} {request.url}",
|
|
197
|
+
backoff=10,
|
|
198
|
+
),
|
|
199
|
+
505: ErrorStatus(
|
|
200
|
+
template=r"[505] HTTP Version Not Supported {request.method} {request.url}"
|
|
201
|
+
),
|
|
202
|
+
506: ErrorStatus(
|
|
203
|
+
template=r"[506] Variant Also Negotiates {request.method} {request.url}"
|
|
204
|
+
),
|
|
205
|
+
507: ErrorStatus(
|
|
206
|
+
template=r"[507] Insufficient Storage {request.method} {request.url}"
|
|
207
|
+
),
|
|
208
|
+
508: ErrorStatus(template=r"[508] Loop Detected {request.method} {request.url}"),
|
|
209
|
+
510: ErrorStatus(template=r"[510] Not Extended {request.method} {request.url}"),
|
|
210
|
+
511: ErrorStatus(
|
|
211
|
+
template=r"[511] Network Authentication Required {request.method} {request.url}"
|
|
212
|
+
),
|
|
213
|
+
}
|