rapid-api-client 0.3.2__tar.gz → 0.3.4__tar.gz
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.
- {rapid_api_client-0.3.2 → rapid_api_client-0.3.4}/PKG-INFO +7 -7
- {rapid_api_client-0.3.2 → rapid_api_client-0.3.4}/README.md +6 -6
- {rapid_api_client-0.3.2 → rapid_api_client-0.3.4}/pyproject.toml +1 -1
- {rapid_api_client-0.3.2 → rapid_api_client-0.3.4}/rapid_api_client/__init__.py +3 -2
- rapid_api_client-0.3.2/rapid_api_client/model.py → rapid_api_client-0.3.4/rapid_api_client/annotations.py +11 -24
- rapid_api_client-0.3.4/rapid_api_client/client.py +138 -0
- rapid_api_client-0.3.4/rapid_api_client/decorator.py +103 -0
- {rapid_api_client-0.3.2 → rapid_api_client-0.3.4}/rapid_api_client/utils.py +3 -3
- rapid_api_client-0.3.2/rapid_api_client/client.py +0 -218
- {rapid_api_client-0.3.2 → rapid_api_client-0.3.4}/LICENSE +0 -0
- {rapid_api_client-0.3.2 → rapid_api_client-0.3.4}/rapid_api_client/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: rapid-api-client
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Rapidly develop your API clients using decorators and annotations
|
|
5
5
|
Author: Sébastien MB
|
|
6
6
|
Author-email: seb@essembeh.org
|
|
@@ -134,7 +134,7 @@ class MyApi(RapidApi)
|
|
|
134
134
|
|
|
135
135
|
# this method return a httpx.Response
|
|
136
136
|
@get("/user/me")
|
|
137
|
-
async def
|
|
137
|
+
async def get_user_raw(self): ...
|
|
138
138
|
|
|
139
139
|
# this method returns a User class
|
|
140
140
|
@get("/user/me", response_class=User)
|
|
@@ -190,11 +190,11 @@ class MyApi(RapidApi)
|
|
|
190
190
|
|
|
191
191
|
# Headers can have a default value
|
|
192
192
|
@get("/issues")
|
|
193
|
-
async def
|
|
193
|
+
async def get_issues_default(self, version: Annotated[str, Header()] = "1"): ...
|
|
194
194
|
|
|
195
195
|
# Headers can have an alias to change the key in the http request
|
|
196
196
|
@get("/issues")
|
|
197
|
-
async def
|
|
197
|
+
async def get_issues_version(self, version: Annotated[str, Header(alias="X-API-Version")] = "1"): ...
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
## Body parameter
|
|
@@ -211,15 +211,15 @@ class MyApi(RapidApi)
|
|
|
211
211
|
|
|
212
212
|
# send a string in request content
|
|
213
213
|
@post("/string")
|
|
214
|
-
async def
|
|
214
|
+
async def post_string(self, body: Annotated[str, Body()]): ...
|
|
215
215
|
|
|
216
216
|
# send a string in request content
|
|
217
217
|
@post("/model")
|
|
218
|
-
async def
|
|
218
|
+
async def post_model(self, body: Annotated[MyPydanticClass, PydanticBody()]): ...
|
|
219
219
|
|
|
220
220
|
# send a multiple files
|
|
221
221
|
@post("/files")
|
|
222
|
-
async def
|
|
222
|
+
async def post_files(self, report: Annotated[bytes, FileBody()], image: Annotated[bytes, FileBody()]): ...
|
|
223
223
|
|
|
224
224
|
```
|
|
225
225
|
|
|
@@ -102,7 +102,7 @@ class MyApi(RapidApi)
|
|
|
102
102
|
|
|
103
103
|
# this method return a httpx.Response
|
|
104
104
|
@get("/user/me")
|
|
105
|
-
async def
|
|
105
|
+
async def get_user_raw(self): ...
|
|
106
106
|
|
|
107
107
|
# this method returns a User class
|
|
108
108
|
@get("/user/me", response_class=User)
|
|
@@ -158,11 +158,11 @@ class MyApi(RapidApi)
|
|
|
158
158
|
|
|
159
159
|
# Headers can have a default value
|
|
160
160
|
@get("/issues")
|
|
161
|
-
async def
|
|
161
|
+
async def get_issues_default(self, version: Annotated[str, Header()] = "1"): ...
|
|
162
162
|
|
|
163
163
|
# Headers can have an alias to change the key in the http request
|
|
164
164
|
@get("/issues")
|
|
165
|
-
async def
|
|
165
|
+
async def get_issues_version(self, version: Annotated[str, Header(alias="X-API-Version")] = "1"): ...
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
## Body parameter
|
|
@@ -179,15 +179,15 @@ class MyApi(RapidApi)
|
|
|
179
179
|
|
|
180
180
|
# send a string in request content
|
|
181
181
|
@post("/string")
|
|
182
|
-
async def
|
|
182
|
+
async def post_string(self, body: Annotated[str, Body()]): ...
|
|
183
183
|
|
|
184
184
|
# send a string in request content
|
|
185
185
|
@post("/model")
|
|
186
|
-
async def
|
|
186
|
+
async def post_model(self, body: Annotated[MyPydanticClass, PydanticBody()]): ...
|
|
187
187
|
|
|
188
188
|
# send a multiple files
|
|
189
189
|
@post("/files")
|
|
190
|
-
async def
|
|
190
|
+
async def post_files(self, report: Annotated[bytes, FileBody()], image: Annotated[bytes, FileBody()]): ...
|
|
191
191
|
|
|
192
192
|
```
|
|
193
193
|
|
|
@@ -4,8 +4,9 @@ rapid-api-client
|
|
|
4
4
|
|
|
5
5
|
from importlib.metadata import version
|
|
6
6
|
|
|
7
|
-
from .
|
|
8
|
-
from .
|
|
7
|
+
from .annotations import Body, FileBody, Header, Path, PydanticBody, Query
|
|
8
|
+
from .client import RapidApi
|
|
9
|
+
from .decorator import delete, get, http, patch, post, put
|
|
9
10
|
|
|
10
11
|
__version__ = version(__name__)
|
|
11
12
|
__all__ = [
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
Model classes
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from typing import Any
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Literal
|
|
7
7
|
|
|
8
|
-
from httpx import AsyncClient
|
|
9
8
|
from pydantic import BaseModel
|
|
10
9
|
|
|
11
10
|
try:
|
|
@@ -14,30 +13,20 @@ except ImportError: # pragma: nocover
|
|
|
14
13
|
pydantic_xml = None # type: ignore
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
class RapidApi:
|
|
19
|
-
"""
|
|
20
|
-
Represent an API, a RapidApi subclass should have methods decorated with @http
|
|
21
|
-
which are endpoints
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
client: AsyncClient = field(default_factory=AsyncClient)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class CustomParameter:
|
|
16
|
+
class BaseAnnotation:
|
|
28
17
|
"""
|
|
29
18
|
Meta class for annotations used to customize the request build
|
|
30
19
|
"""
|
|
31
20
|
|
|
32
21
|
|
|
33
|
-
class Path(
|
|
22
|
+
class Path(BaseAnnotation):
|
|
34
23
|
"""
|
|
35
24
|
Annotation to declare an argument used to resolve the api path/url
|
|
36
25
|
"""
|
|
37
26
|
|
|
38
27
|
|
|
39
28
|
@dataclass
|
|
40
|
-
class Query(
|
|
29
|
+
class Query(BaseAnnotation):
|
|
41
30
|
"""
|
|
42
31
|
Annotation to declare an argument used as a query parameter
|
|
43
32
|
"""
|
|
@@ -46,7 +35,7 @@ class Query(CustomParameter):
|
|
|
46
35
|
|
|
47
36
|
|
|
48
37
|
@dataclass
|
|
49
|
-
class Header(
|
|
38
|
+
class Header(BaseAnnotation):
|
|
50
39
|
"""
|
|
51
40
|
Annotation to declare an argument used as a request header
|
|
52
41
|
"""
|
|
@@ -54,11 +43,14 @@ class Header(CustomParameter):
|
|
|
54
43
|
alias: str | None = None
|
|
55
44
|
|
|
56
45
|
|
|
57
|
-
|
|
46
|
+
@dataclass
|
|
47
|
+
class Body(BaseAnnotation):
|
|
58
48
|
"""
|
|
59
49
|
Annotation to declare an argument used as http content for post/put/...
|
|
60
50
|
"""
|
|
61
51
|
|
|
52
|
+
target: Literal["content", "data", "files"] = "content"
|
|
53
|
+
|
|
62
54
|
def serialize(self, body: Any) -> str | bytes:
|
|
63
55
|
"""
|
|
64
56
|
Serialize the annotated parameter value
|
|
@@ -72,14 +64,9 @@ class FileBody(Body):
|
|
|
72
64
|
Annotation to declare an argument used as file to be uploaded
|
|
73
65
|
"""
|
|
74
66
|
|
|
67
|
+
target: Literal["content", "data", "files"] = "files"
|
|
75
68
|
alias: str | None = None
|
|
76
69
|
|
|
77
|
-
def serialize(self, body: Any) -> str | bytes:
|
|
78
|
-
"""
|
|
79
|
-
Serialize the annotated parameter value
|
|
80
|
-
"""
|
|
81
|
-
return body
|
|
82
|
-
|
|
83
70
|
|
|
84
71
|
@dataclass
|
|
85
72
|
class PydanticBody(Body):
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decorator used to build the httpx request
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from inspect import Signature
|
|
7
|
+
from typing import (
|
|
8
|
+
Any,
|
|
9
|
+
Dict,
|
|
10
|
+
Mapping,
|
|
11
|
+
Self,
|
|
12
|
+
Tuple,
|
|
13
|
+
Type,
|
|
14
|
+
TypeVar,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from httpx import AsyncClient, Request, Response
|
|
18
|
+
from pydantic import BaseModel, TypeAdapter
|
|
19
|
+
from pydantic_xml import BaseXmlModel
|
|
20
|
+
|
|
21
|
+
from .annotations import BaseAnnotation, Body, FileBody, Header, Path, Query
|
|
22
|
+
from .utils import filter_none_values, find_annotation
|
|
23
|
+
|
|
24
|
+
BM = TypeVar("BM", bound=BaseModel)
|
|
25
|
+
T = TypeVar("T")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class CustomParameters:
|
|
30
|
+
"""
|
|
31
|
+
Class containing all custom parameters used to build the request.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
path: Dict[str, Path] = field(default_factory=dict)
|
|
35
|
+
query: Dict[str, Query] = field(default_factory=dict)
|
|
36
|
+
headers: Dict[str, Header] = field(default_factory=dict)
|
|
37
|
+
body: Dict[str, Body] = field(default_factory=dict)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_sig(cls, sig: Signature) -> Self:
|
|
41
|
+
"""
|
|
42
|
+
Iterate over parameters of given function to find annotated parameters
|
|
43
|
+
"""
|
|
44
|
+
out = cls()
|
|
45
|
+
for parameter in sig.parameters.values():
|
|
46
|
+
annot: BaseAnnotation | None = None
|
|
47
|
+
if (annot := find_annotation(parameter, Path)) is not None:
|
|
48
|
+
out.path[parameter.name] = annot
|
|
49
|
+
if (annot := find_annotation(parameter, Query)) is not None:
|
|
50
|
+
out.query[parameter.name] = annot
|
|
51
|
+
if (annot := find_annotation(parameter, Header)) is not None:
|
|
52
|
+
out.headers[parameter.name] = annot
|
|
53
|
+
if (annot := find_annotation(parameter, Body)) is not None:
|
|
54
|
+
out.body[parameter.name] = annot
|
|
55
|
+
return out
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class RapidApi:
|
|
60
|
+
"""
|
|
61
|
+
Represent an API, a RapidApi subclass should have methods decorated with @http
|
|
62
|
+
which are endpoints
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
client: AsyncClient = field(default_factory=AsyncClient)
|
|
66
|
+
|
|
67
|
+
def _build_request(
|
|
68
|
+
self,
|
|
69
|
+
sig: Signature,
|
|
70
|
+
parameters: CustomParameters,
|
|
71
|
+
method: str,
|
|
72
|
+
path: str,
|
|
73
|
+
args: Tuple[Any],
|
|
74
|
+
kwargs: Mapping[str, Any],
|
|
75
|
+
timeout: float | None,
|
|
76
|
+
) -> Request:
|
|
77
|
+
"""
|
|
78
|
+
Build the httpx request with given custom parameters.
|
|
79
|
+
"""
|
|
80
|
+
# valuate arguments with default values
|
|
81
|
+
ba = sig.bind(*args, **kwargs)
|
|
82
|
+
ba.apply_defaults()
|
|
83
|
+
|
|
84
|
+
# resolve the api path
|
|
85
|
+
path = path.format(**{k: ba.arguments[k] for k in parameters.path})
|
|
86
|
+
|
|
87
|
+
build_kwargs: Dict[str, Any] = {}
|
|
88
|
+
|
|
89
|
+
build_kwargs["headers"] = filter_none_values(
|
|
90
|
+
{
|
|
91
|
+
annot.alias or param: ba.arguments[param]
|
|
92
|
+
for param, annot in parameters.headers.items()
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
build_kwargs["params"] = filter_none_values(
|
|
96
|
+
{
|
|
97
|
+
annot.alias or param: ba.arguments[param]
|
|
98
|
+
for param, annot in parameters.query.items()
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
for param, annot in parameters.body.items():
|
|
102
|
+
if (value := ba.arguments[param]) is not None:
|
|
103
|
+
if isinstance(annot, FileBody):
|
|
104
|
+
files = build_kwargs.setdefault("files", {})
|
|
105
|
+
files[annot.alias or param] = annot.serialize(value)
|
|
106
|
+
else:
|
|
107
|
+
build_kwargs[annot.target] = annot.serialize(value)
|
|
108
|
+
|
|
109
|
+
# handle extra optional kwargs
|
|
110
|
+
if timeout is not None:
|
|
111
|
+
build_kwargs["timeout"] = timeout
|
|
112
|
+
|
|
113
|
+
return self.client.build_request(method, path, **build_kwargs)
|
|
114
|
+
|
|
115
|
+
def _handle_response(
|
|
116
|
+
self,
|
|
117
|
+
response: Response,
|
|
118
|
+
response_class: Type[Response | str | bytes | BM] | TypeAdapter[T] = Response,
|
|
119
|
+
) -> Response | str | bytes | BM | T:
|
|
120
|
+
"""
|
|
121
|
+
Parse the response given the expected class
|
|
122
|
+
"""
|
|
123
|
+
# do not check response status code if we return the Response itself
|
|
124
|
+
if response_class is Response:
|
|
125
|
+
return response
|
|
126
|
+
# before parsing the response, check its status
|
|
127
|
+
response.raise_for_status()
|
|
128
|
+
if response_class is str:
|
|
129
|
+
return response.text
|
|
130
|
+
if response_class is bytes:
|
|
131
|
+
return response.content
|
|
132
|
+
if isinstance(response_class, TypeAdapter):
|
|
133
|
+
return response_class.validate_json(response.content)
|
|
134
|
+
if issubclass(response_class, BaseXmlModel):
|
|
135
|
+
return response_class.from_xml(response.content)
|
|
136
|
+
if issubclass(response_class, BaseModel):
|
|
137
|
+
return response_class.model_validate_json(response.content)
|
|
138
|
+
raise ValueError(f"Response class not supported: {response_class}")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decorator used to build the request and send it with httpx
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from functools import partial, wraps
|
|
6
|
+
from inspect import signature
|
|
7
|
+
from typing import (
|
|
8
|
+
Any,
|
|
9
|
+
Callable,
|
|
10
|
+
Coroutine,
|
|
11
|
+
Type,
|
|
12
|
+
overload,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from httpx import Response
|
|
16
|
+
from pydantic import TypeAdapter
|
|
17
|
+
|
|
18
|
+
from .client import BM, CustomParameters, RapidApi, T
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@overload
|
|
22
|
+
def http(
|
|
23
|
+
method: str,
|
|
24
|
+
path: str,
|
|
25
|
+
response_class: Type[Response] = Response,
|
|
26
|
+
timeout: float | None = None,
|
|
27
|
+
) -> Callable[[Callable], Callable[..., Coroutine[Any, Any, Response]]]: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@overload
|
|
31
|
+
def http(
|
|
32
|
+
method: str,
|
|
33
|
+
path: str,
|
|
34
|
+
response_class: Type[str],
|
|
35
|
+
timeout: float | None = None,
|
|
36
|
+
) -> Callable[[Callable], Callable[..., Coroutine[Any, Any, str]]]: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def http(
|
|
41
|
+
method: str,
|
|
42
|
+
path: str,
|
|
43
|
+
response_class: Type[bytes],
|
|
44
|
+
timeout: float | None = None,
|
|
45
|
+
) -> Callable[[Callable], Callable[..., Coroutine[Any, Any, bytes]]]: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@overload
|
|
49
|
+
def http(
|
|
50
|
+
method: str,
|
|
51
|
+
path: str,
|
|
52
|
+
response_class: Type[BM],
|
|
53
|
+
timeout: float | None = None,
|
|
54
|
+
) -> Callable[[Callable], Callable[..., Coroutine[Any, Any, BM]]]: ...
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@overload
|
|
58
|
+
def http(
|
|
59
|
+
method: str,
|
|
60
|
+
path: str,
|
|
61
|
+
response_class: TypeAdapter[T],
|
|
62
|
+
timeout: float | None = None,
|
|
63
|
+
) -> Callable[[Callable], Callable[..., Coroutine[Any, Any, T]]]: ...
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def http(
|
|
67
|
+
method: str,
|
|
68
|
+
path: str,
|
|
69
|
+
response_class: Type[BM | str | bytes | Response] | TypeAdapter[T] = Response,
|
|
70
|
+
timeout: float | None = None,
|
|
71
|
+
) -> Callable[
|
|
72
|
+
[Callable], Callable[..., Coroutine[Any, Any, BM | str | bytes | Response | T]]
|
|
73
|
+
]:
|
|
74
|
+
"""
|
|
75
|
+
Main decorator used to generate an http request and return its result
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def decorator(
|
|
79
|
+
func: Callable,
|
|
80
|
+
) -> Callable[..., Coroutine[Any, Any, BM | str | bytes | Response | T]]:
|
|
81
|
+
sig = signature(func)
|
|
82
|
+
custom_parameters = CustomParameters.from_sig(sig)
|
|
83
|
+
|
|
84
|
+
@wraps(func)
|
|
85
|
+
async def wrapper(*args, **kwargs) -> BM | str | bytes | Response | T:
|
|
86
|
+
if not isinstance((api := args[0]), RapidApi):
|
|
87
|
+
raise ValueError(f"{api} should be an instance of RapidApi")
|
|
88
|
+
request = api._build_request(
|
|
89
|
+
sig, custom_parameters, method, path, args, kwargs, timeout
|
|
90
|
+
)
|
|
91
|
+
response = await api.client.send(request)
|
|
92
|
+
return api._handle_response(response, response_class=response_class)
|
|
93
|
+
|
|
94
|
+
return wrapper
|
|
95
|
+
|
|
96
|
+
return decorator
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
get = partial(http, "GET")
|
|
100
|
+
post = partial(http, "POST")
|
|
101
|
+
delete = partial(http, "DELETE")
|
|
102
|
+
put = partial(http, "PUT")
|
|
103
|
+
patch = partial(http, "PATCH")
|
|
@@ -5,9 +5,9 @@ Utility methods
|
|
|
5
5
|
from inspect import Parameter
|
|
6
6
|
from typing import Any, Dict, Type, TypeVar, get_args
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from .annotations import BaseAnnotation
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
BA = TypeVar("BA", bound=BaseAnnotation)
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def filter_none_values(values: Dict[str, Any | None]) -> Dict[str, Any]:
|
|
@@ -17,7 +17,7 @@ def filter_none_values(values: Dict[str, Any | None]) -> Dict[str, Any]:
|
|
|
17
17
|
return {k: v for k, v in values.items() if v is not None}
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def find_annotation(param: Parameter, cls: Type[
|
|
20
|
+
def find_annotation(param: Parameter, cls: Type[BA]) -> BA | None:
|
|
21
21
|
"""
|
|
22
22
|
Check if the given parameter has an annotation which is or is a subclass of given type
|
|
23
23
|
"""
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Decorator used to build the httpx request
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from dataclasses import dataclass, field
|
|
6
|
-
from functools import partial, wraps
|
|
7
|
-
from inspect import Signature, signature
|
|
8
|
-
from typing import (
|
|
9
|
-
Any,
|
|
10
|
-
Awaitable,
|
|
11
|
-
Callable,
|
|
12
|
-
Dict,
|
|
13
|
-
Mapping,
|
|
14
|
-
Self,
|
|
15
|
-
Tuple,
|
|
16
|
-
Type,
|
|
17
|
-
TypeVar,
|
|
18
|
-
overload,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
from httpx import AsyncClient, Request, Response
|
|
22
|
-
from pydantic import BaseModel, TypeAdapter
|
|
23
|
-
from pydantic_xml import BaseXmlModel
|
|
24
|
-
|
|
25
|
-
from .model import Body, CustomParameter, FileBody, Header, Path, Query, RapidApi
|
|
26
|
-
from .utils import filter_none_values, find_annotation
|
|
27
|
-
|
|
28
|
-
BM = TypeVar("BM", bound=BaseModel)
|
|
29
|
-
T = TypeVar("T")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@dataclass
|
|
33
|
-
class CustomParameters:
|
|
34
|
-
"""
|
|
35
|
-
Class containing all custom parameters used to build the request.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
path: Dict[str, Path] = field(default_factory=dict)
|
|
39
|
-
query: Dict[str, Query] = field(default_factory=dict)
|
|
40
|
-
headers: Dict[str, Header] = field(default_factory=dict)
|
|
41
|
-
body: Dict[str, Body] = field(default_factory=dict)
|
|
42
|
-
|
|
43
|
-
@classmethod
|
|
44
|
-
def from_sig(cls, sig: Signature) -> Self:
|
|
45
|
-
"""
|
|
46
|
-
Iterate over parameters of given function to find annotated parameters
|
|
47
|
-
"""
|
|
48
|
-
out = cls()
|
|
49
|
-
for parameter in sig.parameters.values():
|
|
50
|
-
annot: CustomParameter | None = None
|
|
51
|
-
if (annot := find_annotation(parameter, Path)) is not None:
|
|
52
|
-
out.path[parameter.name] = annot
|
|
53
|
-
if (annot := find_annotation(parameter, Query)) is not None:
|
|
54
|
-
out.query[parameter.name] = annot
|
|
55
|
-
if (annot := find_annotation(parameter, Header)) is not None:
|
|
56
|
-
out.headers[parameter.name] = annot
|
|
57
|
-
if (annot := find_annotation(parameter, Body)) is not None:
|
|
58
|
-
out.body[parameter.name] = annot
|
|
59
|
-
return out
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def build_request(
|
|
63
|
-
client: AsyncClient,
|
|
64
|
-
sig: Signature,
|
|
65
|
-
parameters: CustomParameters,
|
|
66
|
-
method: str,
|
|
67
|
-
path: str,
|
|
68
|
-
args: Tuple[Any],
|
|
69
|
-
kwargs: Mapping[str, Any],
|
|
70
|
-
timeout: float | None,
|
|
71
|
-
) -> Request:
|
|
72
|
-
"""
|
|
73
|
-
Build the httpx request with given custom parameters.
|
|
74
|
-
"""
|
|
75
|
-
# valuate arguments with default values
|
|
76
|
-
ba = sig.bind(*args, **kwargs)
|
|
77
|
-
ba.apply_defaults()
|
|
78
|
-
|
|
79
|
-
# resolve the api path
|
|
80
|
-
path = path.format(**{k: ba.arguments[k] for k in parameters.path})
|
|
81
|
-
|
|
82
|
-
build_kwargs: Dict[str, Any] = {}
|
|
83
|
-
|
|
84
|
-
build_kwargs["headers"] = filter_none_values(
|
|
85
|
-
{
|
|
86
|
-
annot.alias or param: ba.arguments[param]
|
|
87
|
-
for param, annot in parameters.headers.items()
|
|
88
|
-
}
|
|
89
|
-
)
|
|
90
|
-
build_kwargs["params"] = filter_none_values(
|
|
91
|
-
{
|
|
92
|
-
annot.alias or param: ba.arguments[param]
|
|
93
|
-
for param, annot in parameters.query.items()
|
|
94
|
-
}
|
|
95
|
-
)
|
|
96
|
-
for param, annot in parameters.body.items():
|
|
97
|
-
if (value := ba.arguments[param]) is not None:
|
|
98
|
-
if isinstance(annot, FileBody):
|
|
99
|
-
files = build_kwargs.setdefault("files", {})
|
|
100
|
-
files[annot.alias or param] = annot.serialize(value)
|
|
101
|
-
else:
|
|
102
|
-
build_kwargs["content"] = annot.serialize(value)
|
|
103
|
-
|
|
104
|
-
# handle extra optional kwargs
|
|
105
|
-
if timeout is not None:
|
|
106
|
-
build_kwargs["timeout"] = timeout
|
|
107
|
-
|
|
108
|
-
return client.build_request(method, path, **build_kwargs)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def handle_response(
|
|
112
|
-
response: Response,
|
|
113
|
-
response_class: Type[Response | str | bytes | BM] | TypeAdapter[T],
|
|
114
|
-
) -> Response | str | bytes | BM | T:
|
|
115
|
-
"""
|
|
116
|
-
Parse the response given the expected class
|
|
117
|
-
"""
|
|
118
|
-
# do not check response status code if we return the Response itself
|
|
119
|
-
if response_class is Response:
|
|
120
|
-
return response
|
|
121
|
-
# before parsing the response, check its status
|
|
122
|
-
response.raise_for_status()
|
|
123
|
-
if response_class is str:
|
|
124
|
-
return response.text
|
|
125
|
-
if response_class is bytes:
|
|
126
|
-
return response.content
|
|
127
|
-
if isinstance(response_class, TypeAdapter):
|
|
128
|
-
return response_class.validate_json(response.content)
|
|
129
|
-
if issubclass(response_class, BaseXmlModel):
|
|
130
|
-
return response_class.from_xml(response.content)
|
|
131
|
-
if issubclass(response_class, BaseModel):
|
|
132
|
-
return response_class.model_validate_json(response.content)
|
|
133
|
-
raise ValueError(f"Response class not supported: {response_class}")
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@overload
|
|
137
|
-
def http(
|
|
138
|
-
method: str,
|
|
139
|
-
path: str,
|
|
140
|
-
response_class: Type[Response] = Response,
|
|
141
|
-
timeout: float | None = None,
|
|
142
|
-
) -> Callable[[Callable], Callable[..., Awaitable[Response]]]: ...
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
@overload
|
|
146
|
-
def http(
|
|
147
|
-
method: str,
|
|
148
|
-
path: str,
|
|
149
|
-
response_class: Type[str],
|
|
150
|
-
timeout: float | None = None,
|
|
151
|
-
) -> Callable[[Callable], Callable[..., Awaitable[str]]]: ...
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
@overload
|
|
155
|
-
def http(
|
|
156
|
-
method: str,
|
|
157
|
-
path: str,
|
|
158
|
-
response_class: Type[bytes],
|
|
159
|
-
timeout: float | None = None,
|
|
160
|
-
) -> Callable[[Callable], Callable[..., Awaitable[bytes]]]: ...
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
@overload
|
|
164
|
-
def http(
|
|
165
|
-
method: str,
|
|
166
|
-
path: str,
|
|
167
|
-
response_class: Type[BM],
|
|
168
|
-
timeout: float | None = None,
|
|
169
|
-
) -> Callable[[Callable], Callable[..., Awaitable[BM]]]: ...
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@overload
|
|
173
|
-
def http(
|
|
174
|
-
method: str,
|
|
175
|
-
path: str,
|
|
176
|
-
response_class: TypeAdapter[T],
|
|
177
|
-
timeout: float | None = None,
|
|
178
|
-
) -> Callable[[Callable], Callable[..., Awaitable[T]]]: ...
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def http(
|
|
182
|
-
method: str,
|
|
183
|
-
path: str,
|
|
184
|
-
response_class: Type[BM | str | bytes | Response] | TypeAdapter[T] = Response,
|
|
185
|
-
timeout: float | None = None,
|
|
186
|
-
) -> Callable[[Callable], Callable[..., Awaitable[BM | str | bytes | Response | T]]]:
|
|
187
|
-
"""
|
|
188
|
-
Main decorator used to generate an http request and return its result
|
|
189
|
-
"""
|
|
190
|
-
|
|
191
|
-
def decorator(
|
|
192
|
-
func: Callable,
|
|
193
|
-
) -> Callable[..., Awaitable[BM | str | bytes | Response | T]]:
|
|
194
|
-
sig = signature(func)
|
|
195
|
-
custom_parameters = CustomParameters.from_sig(sig)
|
|
196
|
-
|
|
197
|
-
@wraps(func)
|
|
198
|
-
async def wrapper(*args, **kwargs) -> BM | str | bytes | Response | T:
|
|
199
|
-
assert isinstance(
|
|
200
|
-
args[0], RapidApi
|
|
201
|
-
), f"{args[0]} should be an instance of RapidApi"
|
|
202
|
-
client = args[0].client
|
|
203
|
-
request = build_request(
|
|
204
|
-
client, sig, custom_parameters, method, path, args, kwargs, timeout
|
|
205
|
-
)
|
|
206
|
-
response = await client.send(request)
|
|
207
|
-
return handle_response(response, response_class)
|
|
208
|
-
|
|
209
|
-
return wrapper
|
|
210
|
-
|
|
211
|
-
return decorator
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
get = partial(http, "GET")
|
|
215
|
-
post = partial(http, "POST")
|
|
216
|
-
delete = partial(http, "DELETE")
|
|
217
|
-
put = partial(http, "PUT")
|
|
218
|
-
patch = partial(http, "PATCH")
|
|
File without changes
|
|
File without changes
|