rapid-api-client 0.2.0__tar.gz → 0.3.1__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.2.0 → rapid_api_client-0.3.1}/PKG-INFO +29 -11
- {rapid_api_client-0.2.0 → rapid_api_client-0.3.1}/README.md +29 -11
- {rapid_api_client-0.2.0 → rapid_api_client-0.3.1}/pyproject.toml +1 -1
- {rapid_api_client-0.2.0 → rapid_api_client-0.3.1}/rapid_api_client/client.py +83 -22
- rapid_api_client-0.3.1/rapid_api_client/py.typed +0 -0
- {rapid_api_client-0.2.0 → rapid_api_client-0.3.1}/LICENSE +0 -0
- {rapid_api_client-0.2.0 → rapid_api_client-0.3.1}/rapid_api_client/__init__.py +0 -0
- {rapid_api_client-0.2.0 → rapid_api_client-0.3.1}/rapid_api_client/model.py +0 -0
- {rapid_api_client-0.2.0 → rapid_api_client-0.3.1}/rapid_api_client/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: rapid-api-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -38,10 +38,15 @@ Description-Content-Type: text/markdown
|
|
|
38
38
|
|
|
39
39
|
# Rapid Api Client
|
|
40
40
|
|
|
41
|
-
Library to rapidly develop
|
|
41
|
+
Library to **rapidly** develop *API clients* in Python, based on [Pydantic](https://docs.pydantic.dev/) and [Httpx](https://www.python-httpx.org/), using almost only *decorators* and *annotations*.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
✨ Main features:
|
|
44
|
+
- ✏️ You don't write any code, you only declare the endpoints using *decorators* and *annotations*.
|
|
45
|
+
- 🚚 Support *Pydantic* to automatically parse and validate reponses (and also for posting content).
|
|
46
|
+
- 🏗️ Does not reimplement the low-level http-related logic, it simply uses `httpx.AsyncClient` like you would do and you can customize it.
|
|
47
|
+
- ⚡️ Asynchronous, because `httpx` and `asyncio` are just amazingly fast.
|
|
44
48
|
|
|
49
|
+
🙏 This project is inspired by [FastAPI](https://fastapi.tiangolo.com/), I always wanted a library to create an API client that is as simple as *FastAPI* for handling the server-side part.
|
|
45
50
|
|
|
46
51
|
# Usage
|
|
47
52
|
|
|
@@ -51,12 +56,15 @@ Install the project
|
|
|
51
56
|
pip install rapid-api-client
|
|
52
57
|
```
|
|
53
58
|
|
|
54
|
-
Declare your API using decorators and annotations
|
|
59
|
+
Declare your API endpoints using decorators and annotations, **the method does not need any code, it will be generated by the decorator**, just write `...` or `pass` or whatever, it won't be called anyway 🙈.
|
|
55
60
|
|
|
56
61
|
```python
|
|
57
62
|
class GithubIssuesApi(RapidApi):
|
|
58
63
|
|
|
59
|
-
@get("/repos/{owner}/{repo}/issues", response_class=
|
|
64
|
+
@get("/repos/{owner}/{repo}/issues", response_class=TypeAdapter(List[Issue]))
|
|
65
|
+
async def list_issues(self, owner: Annotated[str, Path()], repo: Annotated[str, Path()]): ...
|
|
66
|
+
|
|
67
|
+
@get("/repos/{owner}/{repo}/releases", response_class=TypeAdapter(List[Release]))
|
|
60
68
|
async def list_issues(self, owner: Annotated[str, Path()], repo: Annotated[str, Path()]): ...
|
|
61
69
|
|
|
62
70
|
```
|
|
@@ -66,7 +74,7 @@ Use it
|
|
|
66
74
|
```python
|
|
67
75
|
api = GithubIssuesApi(client)
|
|
68
76
|
issues = await api.list_issues("essembeh", "rapid-api-client", state="closed")
|
|
69
|
-
for issue in issues
|
|
77
|
+
for issue in issues:
|
|
70
78
|
print(f"Issue: {issue.title} [{issue.url}]")
|
|
71
79
|
```
|
|
72
80
|
|
|
@@ -79,13 +87,13 @@ Any HTTP method can be used with `http` decorator
|
|
|
79
87
|
```python
|
|
80
88
|
class MyApi(RapidApi)
|
|
81
89
|
|
|
82
|
-
@http("/anything")
|
|
90
|
+
@http("GET", "/anything")
|
|
83
91
|
async def get(self): ...
|
|
84
92
|
|
|
85
|
-
@http("
|
|
93
|
+
@http("POST", "/anything")
|
|
86
94
|
async def post(self): ...
|
|
87
95
|
|
|
88
|
-
@http("
|
|
96
|
+
@http("DELETE", "/anything")
|
|
89
97
|
async def delete(self): ...
|
|
90
98
|
```
|
|
91
99
|
|
|
@@ -109,9 +117,15 @@ class MyApi(RapidApi)
|
|
|
109
117
|
|
|
110
118
|
By default methods return a `httpx.Response` object and the http return code is not tested (you have to call `resp.raise_for_status()` if you need to ensure the response is OK).
|
|
111
119
|
|
|
112
|
-
But you can also specify a
|
|
120
|
+
But you can also specify a class so that the response is parsed, you can use:
|
|
121
|
+
- `httpx.Response` to get the response itself, this is the default behavior
|
|
122
|
+
- `str` to get the `response.text`
|
|
123
|
+
- `bytes` to get the `response.content`
|
|
124
|
+
- Any *Pydantic* model class (subclass of `BaseModel`), the *json* will be automatically validated
|
|
125
|
+
- Any *Pydantic-xml* model class (subclass of `BaseXmlModel`), the *xml* will be automatically validated
|
|
126
|
+
- Any `TypeAdapter` to parse the *json*, see [pydantic doc](https://docs.pydantic.dev/latest/api/type_adapter/)
|
|
113
127
|
|
|
114
|
-
> Note: When
|
|
128
|
+
> Note: When `response_class` is given (and is not `httpx.Response`), the `raise_for_status()` is always called to ensure the http response is OK
|
|
115
129
|
|
|
116
130
|
```python
|
|
117
131
|
class User(BaseModel): ...
|
|
@@ -227,3 +241,7 @@ class MyApi(RapidApi)
|
|
|
227
241
|
async def post_xml(self, body: Annotated[ResponseXmlRootModel, PydanticXmlBody()]): ...
|
|
228
242
|
|
|
229
243
|
```
|
|
244
|
+
|
|
245
|
+
# Examples
|
|
246
|
+
|
|
247
|
+
See [example directory](./examples/) for some examples
|
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
# Rapid Api Client
|
|
8
8
|
|
|
9
|
-
Library to rapidly develop
|
|
9
|
+
Library to **rapidly** develop *API clients* in Python, based on [Pydantic](https://docs.pydantic.dev/) and [Httpx](https://www.python-httpx.org/), using almost only *decorators* and *annotations*.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
✨ Main features:
|
|
12
|
+
- ✏️ You don't write any code, you only declare the endpoints using *decorators* and *annotations*.
|
|
13
|
+
- 🚚 Support *Pydantic* to automatically parse and validate reponses (and also for posting content).
|
|
14
|
+
- 🏗️ Does not reimplement the low-level http-related logic, it simply uses `httpx.AsyncClient` like you would do and you can customize it.
|
|
15
|
+
- ⚡️ Asynchronous, because `httpx` and `asyncio` are just amazingly fast.
|
|
12
16
|
|
|
17
|
+
🙏 This project is inspired by [FastAPI](https://fastapi.tiangolo.com/), I always wanted a library to create an API client that is as simple as *FastAPI* for handling the server-side part.
|
|
13
18
|
|
|
14
19
|
# Usage
|
|
15
20
|
|
|
@@ -19,12 +24,15 @@ Install the project
|
|
|
19
24
|
pip install rapid-api-client
|
|
20
25
|
```
|
|
21
26
|
|
|
22
|
-
Declare your API using decorators and annotations
|
|
27
|
+
Declare your API endpoints using decorators and annotations, **the method does not need any code, it will be generated by the decorator**, just write `...` or `pass` or whatever, it won't be called anyway 🙈.
|
|
23
28
|
|
|
24
29
|
```python
|
|
25
30
|
class GithubIssuesApi(RapidApi):
|
|
26
31
|
|
|
27
|
-
@get("/repos/{owner}/{repo}/issues", response_class=
|
|
32
|
+
@get("/repos/{owner}/{repo}/issues", response_class=TypeAdapter(List[Issue]))
|
|
33
|
+
async def list_issues(self, owner: Annotated[str, Path()], repo: Annotated[str, Path()]): ...
|
|
34
|
+
|
|
35
|
+
@get("/repos/{owner}/{repo}/releases", response_class=TypeAdapter(List[Release]))
|
|
28
36
|
async def list_issues(self, owner: Annotated[str, Path()], repo: Annotated[str, Path()]): ...
|
|
29
37
|
|
|
30
38
|
```
|
|
@@ -34,7 +42,7 @@ Use it
|
|
|
34
42
|
```python
|
|
35
43
|
api = GithubIssuesApi(client)
|
|
36
44
|
issues = await api.list_issues("essembeh", "rapid-api-client", state="closed")
|
|
37
|
-
for issue in issues
|
|
45
|
+
for issue in issues:
|
|
38
46
|
print(f"Issue: {issue.title} [{issue.url}]")
|
|
39
47
|
```
|
|
40
48
|
|
|
@@ -47,13 +55,13 @@ Any HTTP method can be used with `http` decorator
|
|
|
47
55
|
```python
|
|
48
56
|
class MyApi(RapidApi)
|
|
49
57
|
|
|
50
|
-
@http("/anything")
|
|
58
|
+
@http("GET", "/anything")
|
|
51
59
|
async def get(self): ...
|
|
52
60
|
|
|
53
|
-
@http("
|
|
61
|
+
@http("POST", "/anything")
|
|
54
62
|
async def post(self): ...
|
|
55
63
|
|
|
56
|
-
@http("
|
|
64
|
+
@http("DELETE", "/anything")
|
|
57
65
|
async def delete(self): ...
|
|
58
66
|
```
|
|
59
67
|
|
|
@@ -77,9 +85,15 @@ class MyApi(RapidApi)
|
|
|
77
85
|
|
|
78
86
|
By default methods return a `httpx.Response` object and the http return code is not tested (you have to call `resp.raise_for_status()` if you need to ensure the response is OK).
|
|
79
87
|
|
|
80
|
-
But you can also specify a
|
|
88
|
+
But you can also specify a class so that the response is parsed, you can use:
|
|
89
|
+
- `httpx.Response` to get the response itself, this is the default behavior
|
|
90
|
+
- `str` to get the `response.text`
|
|
91
|
+
- `bytes` to get the `response.content`
|
|
92
|
+
- Any *Pydantic* model class (subclass of `BaseModel`), the *json* will be automatically validated
|
|
93
|
+
- Any *Pydantic-xml* model class (subclass of `BaseXmlModel`), the *xml* will be automatically validated
|
|
94
|
+
- Any `TypeAdapter` to parse the *json*, see [pydantic doc](https://docs.pydantic.dev/latest/api/type_adapter/)
|
|
81
95
|
|
|
82
|
-
> Note: When
|
|
96
|
+
> Note: When `response_class` is given (and is not `httpx.Response`), the `raise_for_status()` is always called to ensure the http response is OK
|
|
83
97
|
|
|
84
98
|
```python
|
|
85
99
|
class User(BaseModel): ...
|
|
@@ -194,4 +208,8 @@ class MyApi(RapidApi)
|
|
|
194
208
|
@post("/post")
|
|
195
209
|
async def post_xml(self, body: Annotated[ResponseXmlRootModel, PydanticXmlBody()]): ...
|
|
196
210
|
|
|
197
|
-
```
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
# Examples
|
|
214
|
+
|
|
215
|
+
See [example directory](./examples/) for some examples
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
from functools import partial, wraps
|
|
3
3
|
from inspect import Signature, signature
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Awaitable,
|
|
7
|
+
Callable,
|
|
8
|
+
Dict,
|
|
9
|
+
Mapping,
|
|
10
|
+
Self,
|
|
11
|
+
Tuple,
|
|
12
|
+
Type,
|
|
13
|
+
TypeVar,
|
|
14
|
+
overload,
|
|
15
|
+
)
|
|
5
16
|
|
|
6
17
|
from httpx import AsyncClient, Request, Response
|
|
7
|
-
from pydantic import BaseModel
|
|
18
|
+
from pydantic import BaseModel, TypeAdapter
|
|
19
|
+
from pydantic_xml import BaseXmlModel
|
|
8
20
|
|
|
9
|
-
from .model import Body, FileBody, Header, Path, Query, RapidApi
|
|
21
|
+
from .model import Body, FileBody, Header, Path, Query, RapidApi
|
|
10
22
|
from .utils import filter_none_values, find_annotation
|
|
11
23
|
|
|
12
|
-
|
|
24
|
+
BM = TypeVar("BM", bound=BaseModel)
|
|
25
|
+
T = TypeVar("T")
|
|
13
26
|
|
|
14
27
|
|
|
15
28
|
@dataclass
|
|
@@ -76,15 +89,71 @@ def build_request(
|
|
|
76
89
|
)
|
|
77
90
|
|
|
78
91
|
|
|
92
|
+
def handle_response(
|
|
93
|
+
response: Response,
|
|
94
|
+
response_class: Type[Response | str | bytes | BM] | TypeAdapter[T],
|
|
95
|
+
) -> Response | str | bytes | BM | T:
|
|
96
|
+
# do not check response status code if we return the Response itself
|
|
97
|
+
if response_class is Response:
|
|
98
|
+
return response
|
|
99
|
+
# before parsing the response, check its status
|
|
100
|
+
response.raise_for_status()
|
|
101
|
+
if response_class is str:
|
|
102
|
+
return response.text
|
|
103
|
+
if response_class is bytes:
|
|
104
|
+
return response.content
|
|
105
|
+
if isinstance(response_class, TypeAdapter):
|
|
106
|
+
return response_class.validate_json(response.content)
|
|
107
|
+
if issubclass(response_class, BaseXmlModel):
|
|
108
|
+
return response_class.from_xml(response.content)
|
|
109
|
+
if issubclass(response_class, BaseModel):
|
|
110
|
+
return response_class.model_validate_json(response.content)
|
|
111
|
+
raise ValueError(f"Response class not supported: {response_class}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@overload
|
|
79
115
|
def http(
|
|
80
|
-
|
|
81
|
-
) -> Callable[[Callable], Callable[..., Awaitable[
|
|
82
|
-
|
|
116
|
+
method: str, path: str, response_class: Type[Response] = Response
|
|
117
|
+
) -> Callable[[Callable], Callable[..., Awaitable[Response]]]: ...
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@overload
|
|
121
|
+
def http(
|
|
122
|
+
method: str, path: str, response_class: Type[str]
|
|
123
|
+
) -> Callable[[Callable], Callable[..., Awaitable[str]]]: ...
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@overload
|
|
127
|
+
def http(
|
|
128
|
+
method: str, path: str, response_class: Type[bytes]
|
|
129
|
+
) -> Callable[[Callable], Callable[..., Awaitable[bytes]]]: ...
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@overload
|
|
133
|
+
def http(
|
|
134
|
+
method: str, path: str, response_class: Type[BM]
|
|
135
|
+
) -> Callable[[Callable], Callable[..., Awaitable[BM]]]: ...
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@overload
|
|
139
|
+
def http(
|
|
140
|
+
method: str, path: str, response_class: TypeAdapter[T]
|
|
141
|
+
) -> Callable[[Callable], Callable[..., Awaitable[T]]]: ...
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def http(
|
|
145
|
+
method: str,
|
|
146
|
+
path: str,
|
|
147
|
+
response_class: Type[BM | str | bytes | Response] | TypeAdapter[T] = Response,
|
|
148
|
+
) -> Callable[[Callable], Callable[..., Awaitable[BM | str | bytes | Response | T]]]:
|
|
149
|
+
def decorator(
|
|
150
|
+
func: Callable,
|
|
151
|
+
) -> Callable[..., Awaitable[BM | str | bytes | Response | T]]:
|
|
83
152
|
sig = signature(func)
|
|
84
153
|
custom_parameters = CustomParameters.from_sig(sig)
|
|
85
154
|
|
|
86
155
|
@wraps(func)
|
|
87
|
-
async def wrapper(*args, **kwargs) ->
|
|
156
|
+
async def wrapper(*args, **kwargs) -> BM | str | bytes | Response | T:
|
|
88
157
|
assert isinstance(
|
|
89
158
|
args[0], RapidApi
|
|
90
159
|
), f"{args[0]} should be an instance of RapidApi"
|
|
@@ -93,23 +162,15 @@ def http(
|
|
|
93
162
|
client, sig, custom_parameters, method, path, args, kwargs
|
|
94
163
|
)
|
|
95
164
|
response = await client.send(request)
|
|
96
|
-
|
|
97
|
-
response_class, pydantic_xml.BaseXmlModel
|
|
98
|
-
):
|
|
99
|
-
response.raise_for_status()
|
|
100
|
-
return response_class.from_xml(response.content)
|
|
101
|
-
if issubclass(response_class, BaseModel):
|
|
102
|
-
response.raise_for_status()
|
|
103
|
-
return response_class.model_validate_json(response.content)
|
|
104
|
-
return response
|
|
165
|
+
return handle_response(response, response_class)
|
|
105
166
|
|
|
106
167
|
return wrapper
|
|
107
168
|
|
|
108
169
|
return decorator
|
|
109
170
|
|
|
110
171
|
|
|
111
|
-
get = partial(http,
|
|
112
|
-
post = partial(http,
|
|
113
|
-
delete = partial(http,
|
|
114
|
-
put = partial(http,
|
|
115
|
-
patch = partial(http,
|
|
172
|
+
get = partial(http, "GET")
|
|
173
|
+
post = partial(http, "POST")
|
|
174
|
+
delete = partial(http, "DELETE")
|
|
175
|
+
put = partial(http, "PUT")
|
|
176
|
+
patch = partial(http, "PATCH")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|