rapid-api-client 0.1.0__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.1.0/LICENSE +21 -0
- rapid_api_client-0.1.0/PKG-INFO +186 -0
- rapid_api_client-0.1.0/README.md +153 -0
- rapid_api_client-0.1.0/pyproject.toml +49 -0
- rapid_api_client-0.1.0/rapid_api_client/__init__.py +21 -0
- rapid_api_client-0.1.0/rapid_api_client/client.py +110 -0
- rapid_api_client-0.1.0/rapid_api_client/model.py +48 -0
- rapid_api_client-0.1.0/rapid_api_client/utils.py +18 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Sébastien MB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: rapid-api-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Rapidly develop your API clients using decorators and annotations
|
|
5
|
+
Author: Sébastien MB
|
|
6
|
+
Author-email: seb@essembeh.org
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Web Environment
|
|
10
|
+
Classifier: Framework :: AsyncIO
|
|
11
|
+
Classifier: Framework :: Pydantic
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Topic :: Internet
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Topic :: Software Development
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Dist: httpx (>=0.27.2,<0.28.0)
|
|
30
|
+
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+

|
|
35
|
+

|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Rapid Api Client
|
|
40
|
+
|
|
41
|
+
Library to rapidly develop API clients based on [Pydantic](https://docs.pydantic.dev/) and [Httpx](https://www.python-httpx.org/) using *decorators* and *annotations*.
|
|
42
|
+
|
|
43
|
+
This project is largely inspired by [FastAPI](https://fastapi.tiangolo.com/).
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Usage
|
|
47
|
+
|
|
48
|
+
Install the project
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
pip install rapid-api-client
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Declare your API using decorators and annotations (the method does not need any code, it will be generated by the decorator)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
class GithubIssuesApi(RapidApi):
|
|
58
|
+
|
|
59
|
+
@get("/repos/{owner}/{repo}/issues", response_class=RootModel[List[Issue]])
|
|
60
|
+
def list_issues(self, owner: Annotated[str, Path()], repo: Annotated[str, Path()]): ...
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Use it
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
for issue in GithubIssuesApi().list_issues("essembeh", "rapid-api-client").root:
|
|
68
|
+
print("Issue:", issue)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
# Features
|
|
72
|
+
|
|
73
|
+
## Http method
|
|
74
|
+
|
|
75
|
+
Any HTTP method can be used with `http` decorator
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
class MyApi(RapidApi)
|
|
79
|
+
|
|
80
|
+
@http("/anything") # default is GET
|
|
81
|
+
def get(self): ...
|
|
82
|
+
|
|
83
|
+
@http("/anything", method="POST")
|
|
84
|
+
def post(self): ...
|
|
85
|
+
|
|
86
|
+
@http("/anything", method="DELETE)
|
|
87
|
+
def delete(self): ...
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Convenient decorators are available like `get`, `post`, `delete`, `put`, `patch`
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
class MyApi(RapidApi)
|
|
94
|
+
|
|
95
|
+
@get("/anything")
|
|
96
|
+
def get(self): ...
|
|
97
|
+
|
|
98
|
+
@post("/anything")
|
|
99
|
+
def post(self): ...
|
|
100
|
+
|
|
101
|
+
@delete("/anything")
|
|
102
|
+
def delete(self): ...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## Response class
|
|
107
|
+
|
|
108
|
+
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).
|
|
109
|
+
|
|
110
|
+
But you can also specify a *Pydantic model class* to automatically parse the response.
|
|
111
|
+
|
|
112
|
+
> Note: When a `response_class` is given, the `raise_for_status()` is always called to ensure the http response is OK
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
class User(BaseModel): ...
|
|
116
|
+
|
|
117
|
+
class MyApi(RapidApi)
|
|
118
|
+
|
|
119
|
+
# this method return a httpx.Response
|
|
120
|
+
@get("/user/me")
|
|
121
|
+
def get_user_resp(self): ...
|
|
122
|
+
|
|
123
|
+
# this method returns a User class
|
|
124
|
+
@get("/user/me", response_class=User)
|
|
125
|
+
def get_user(self): ...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## Path parameters
|
|
130
|
+
|
|
131
|
+
Like `fastapi` you can use your method arguments to build the api path to call.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
class MyApi(RapidApi)
|
|
135
|
+
|
|
136
|
+
@get("/user/{user_id}")
|
|
137
|
+
def get_user(self, user_id: Annotated[int, Path()]): ...
|
|
138
|
+
|
|
139
|
+
# Path parameters dans have a default value
|
|
140
|
+
@get("/user/{user_id}")
|
|
141
|
+
def get_user(self, user_id: Annotated[int, Path()] = 1): ...
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Query parameters
|
|
146
|
+
|
|
147
|
+
You can add `query parameters` to your request using the `Query` annotation.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
class MyApi(RapidApi)
|
|
151
|
+
|
|
152
|
+
@get("/issues")
|
|
153
|
+
def get_issues(self, sort: Annotated[str, Query()]): ...
|
|
154
|
+
|
|
155
|
+
# Query parameters can have a default value
|
|
156
|
+
@get("/issues")
|
|
157
|
+
def get_issues_default(self, sort: Annotated[str, Query()] = "date"): ...
|
|
158
|
+
|
|
159
|
+
# Query parameters can have an alias to change the key in the http request
|
|
160
|
+
@get("/issues")
|
|
161
|
+
def get_issues_alias(self, sort: Annotated[str, Query(alias="sort-by")] = "date"): ...
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
## Header parameter
|
|
166
|
+
|
|
167
|
+
You can add `headers` to your request using the `Header` annotation.
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
class MyApi(RapidApi)
|
|
171
|
+
|
|
172
|
+
@get("/issues")
|
|
173
|
+
def get_issues(self, version: Annotated[str, Header()]): ...
|
|
174
|
+
|
|
175
|
+
# Headers can have a default value
|
|
176
|
+
@get("/issues")
|
|
177
|
+
def get_issues(self, version: Annotated[str, Header()] = "1"): ...
|
|
178
|
+
|
|
179
|
+
# Headers can have an alias to change the key in the http request
|
|
180
|
+
@get("/issues")
|
|
181
|
+
def get_issues(self, version: Annotated[str, Header(alias="X-API-Version")] = "1"): ...
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Body parameter
|
|
185
|
+
|
|
186
|
+
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+

|
|
2
|
+

|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Rapid Api Client
|
|
8
|
+
|
|
9
|
+
Library to rapidly develop API clients based on [Pydantic](https://docs.pydantic.dev/) and [Httpx](https://www.python-httpx.org/) using *decorators* and *annotations*.
|
|
10
|
+
|
|
11
|
+
This project is largely inspired by [FastAPI](https://fastapi.tiangolo.com/).
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Usage
|
|
15
|
+
|
|
16
|
+
Install the project
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pip install rapid-api-client
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Declare your API using decorators and annotations (the method does not need any code, it will be generated by the decorator)
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
class GithubIssuesApi(RapidApi):
|
|
26
|
+
|
|
27
|
+
@get("/repos/{owner}/{repo}/issues", response_class=RootModel[List[Issue]])
|
|
28
|
+
def list_issues(self, owner: Annotated[str, Path()], repo: Annotated[str, Path()]): ...
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Use it
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
for issue in GithubIssuesApi().list_issues("essembeh", "rapid-api-client").root:
|
|
36
|
+
print("Issue:", issue)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
# Features
|
|
40
|
+
|
|
41
|
+
## Http method
|
|
42
|
+
|
|
43
|
+
Any HTTP method can be used with `http` decorator
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
class MyApi(RapidApi)
|
|
47
|
+
|
|
48
|
+
@http("/anything") # default is GET
|
|
49
|
+
def get(self): ...
|
|
50
|
+
|
|
51
|
+
@http("/anything", method="POST")
|
|
52
|
+
def post(self): ...
|
|
53
|
+
|
|
54
|
+
@http("/anything", method="DELETE)
|
|
55
|
+
def delete(self): ...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Convenient decorators are available like `get`, `post`, `delete`, `put`, `patch`
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
class MyApi(RapidApi)
|
|
62
|
+
|
|
63
|
+
@get("/anything")
|
|
64
|
+
def get(self): ...
|
|
65
|
+
|
|
66
|
+
@post("/anything")
|
|
67
|
+
def post(self): ...
|
|
68
|
+
|
|
69
|
+
@delete("/anything")
|
|
70
|
+
def delete(self): ...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Response class
|
|
75
|
+
|
|
76
|
+
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).
|
|
77
|
+
|
|
78
|
+
But you can also specify a *Pydantic model class* to automatically parse the response.
|
|
79
|
+
|
|
80
|
+
> Note: When a `response_class` is given, the `raise_for_status()` is always called to ensure the http response is OK
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
class User(BaseModel): ...
|
|
84
|
+
|
|
85
|
+
class MyApi(RapidApi)
|
|
86
|
+
|
|
87
|
+
# this method return a httpx.Response
|
|
88
|
+
@get("/user/me")
|
|
89
|
+
def get_user_resp(self): ...
|
|
90
|
+
|
|
91
|
+
# this method returns a User class
|
|
92
|
+
@get("/user/me", response_class=User)
|
|
93
|
+
def get_user(self): ...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
## Path parameters
|
|
98
|
+
|
|
99
|
+
Like `fastapi` you can use your method arguments to build the api path to call.
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
class MyApi(RapidApi)
|
|
103
|
+
|
|
104
|
+
@get("/user/{user_id}")
|
|
105
|
+
def get_user(self, user_id: Annotated[int, Path()]): ...
|
|
106
|
+
|
|
107
|
+
# Path parameters dans have a default value
|
|
108
|
+
@get("/user/{user_id}")
|
|
109
|
+
def get_user(self, user_id: Annotated[int, Path()] = 1): ...
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Query parameters
|
|
114
|
+
|
|
115
|
+
You can add `query parameters` to your request using the `Query` annotation.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
class MyApi(RapidApi)
|
|
119
|
+
|
|
120
|
+
@get("/issues")
|
|
121
|
+
def get_issues(self, sort: Annotated[str, Query()]): ...
|
|
122
|
+
|
|
123
|
+
# Query parameters can have a default value
|
|
124
|
+
@get("/issues")
|
|
125
|
+
def get_issues_default(self, sort: Annotated[str, Query()] = "date"): ...
|
|
126
|
+
|
|
127
|
+
# Query parameters can have an alias to change the key in the http request
|
|
128
|
+
@get("/issues")
|
|
129
|
+
def get_issues_alias(self, sort: Annotated[str, Query(alias="sort-by")] = "date"): ...
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
## Header parameter
|
|
134
|
+
|
|
135
|
+
You can add `headers` to your request using the `Header` annotation.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
class MyApi(RapidApi)
|
|
139
|
+
|
|
140
|
+
@get("/issues")
|
|
141
|
+
def get_issues(self, version: Annotated[str, Header()]): ...
|
|
142
|
+
|
|
143
|
+
# Headers can have a default value
|
|
144
|
+
@get("/issues")
|
|
145
|
+
def get_issues(self, version: Annotated[str, Header()] = "1"): ...
|
|
146
|
+
|
|
147
|
+
# Headers can have an alias to change the key in the http request
|
|
148
|
+
@get("/issues")
|
|
149
|
+
def get_issues(self, version: Annotated[str, Header(alias="X-API-Version")] = "1"): ...
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Body parameter
|
|
153
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "rapid-api-client"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Rapidly develop your API clients using decorators and annotations"
|
|
5
|
+
authors = ["Sébastien MB <seb@essembeh.org>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Intended Audience :: Information Technology",
|
|
9
|
+
"Intended Audience :: System Administrators",
|
|
10
|
+
"Operating System :: OS Independent",
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Programming Language :: Python",
|
|
13
|
+
"Topic :: Internet",
|
|
14
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
15
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
16
|
+
"Topic :: Software Development :: Libraries",
|
|
17
|
+
"Topic :: Software Development",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Environment :: Web Environment",
|
|
21
|
+
"Framework :: AsyncIO",
|
|
22
|
+
"Framework :: Pydantic",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/essembeh/rapid-api-client"
|
|
33
|
+
Repository = "https://github.com/essembeh/rapid-api-client"
|
|
34
|
+
Issues = "https://github.com/essembeh/rapid-api-client/issues"
|
|
35
|
+
|
|
36
|
+
[tool.poetry.dependencies]
|
|
37
|
+
python = "^3.11"
|
|
38
|
+
pydantic = "^2.9.2"
|
|
39
|
+
httpx = "^0.27.2"
|
|
40
|
+
|
|
41
|
+
[tool.poetry.group.dev.dependencies]
|
|
42
|
+
pytest = "^8.3.3"
|
|
43
|
+
pytest-asyncio = "^0.24.0"
|
|
44
|
+
pytest-cov = "^5.0.0"
|
|
45
|
+
pytest-dotenv = {extras = ["cli"], version = "^0.5.2"}
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["poetry-core"]
|
|
49
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
3
|
+
from .client import delete, get, http, patch, post, put
|
|
4
|
+
from .model import Body, FileBody, Header, Path, PydanticBody, Query, RapidApi
|
|
5
|
+
|
|
6
|
+
__version__ = version(__name__)
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Body",
|
|
9
|
+
"FileBody",
|
|
10
|
+
"Header",
|
|
11
|
+
"Path",
|
|
12
|
+
"PydanticBody",
|
|
13
|
+
"Query",
|
|
14
|
+
"RapidApi",
|
|
15
|
+
"get",
|
|
16
|
+
"post",
|
|
17
|
+
"put",
|
|
18
|
+
"delete",
|
|
19
|
+
"patch",
|
|
20
|
+
"http",
|
|
21
|
+
]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from functools import partial, wraps
|
|
3
|
+
from inspect import Signature, signature
|
|
4
|
+
from typing import Any, Callable, Dict, Mapping, Self, Tuple, Type, TypeVar
|
|
5
|
+
|
|
6
|
+
from httpx import Client, Request, Response
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from .model import Body, FileBody, Header, Path, Query, RapidApi
|
|
10
|
+
from .utils import filter_none_values, find_annotation
|
|
11
|
+
|
|
12
|
+
RESP = TypeVar("RESP", bound=BaseModel | Response)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class CustomParameters:
|
|
17
|
+
path: Dict[str, Path] = field(default_factory=dict)
|
|
18
|
+
query: Dict[str, Query] = field(default_factory=dict)
|
|
19
|
+
headers: Dict[str, Header] = field(default_factory=dict)
|
|
20
|
+
body: Dict[str, Body] = field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_sig(cls, signature: Signature) -> Self:
|
|
24
|
+
out = cls()
|
|
25
|
+
for parameter in signature.parameters.values():
|
|
26
|
+
if (annot := find_annotation(parameter, Path)) is not None:
|
|
27
|
+
out.path[parameter.name] = annot
|
|
28
|
+
if (annot := find_annotation(parameter, Query)) is not None:
|
|
29
|
+
out.query[parameter.name] = annot
|
|
30
|
+
if (annot := find_annotation(parameter, Header)) is not None:
|
|
31
|
+
out.headers[parameter.name] = annot
|
|
32
|
+
if (annot := find_annotation(parameter, Body)) is not None:
|
|
33
|
+
out.body[parameter.name] = annot
|
|
34
|
+
return out
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_request(
|
|
38
|
+
client: Client,
|
|
39
|
+
signature: Signature,
|
|
40
|
+
parameters: CustomParameters,
|
|
41
|
+
method: str,
|
|
42
|
+
path: str,
|
|
43
|
+
args: Tuple[Any],
|
|
44
|
+
kwargs: Mapping[str, Any],
|
|
45
|
+
) -> Request:
|
|
46
|
+
# valuate arguments with default values
|
|
47
|
+
ba = signature.bind(*args, **kwargs)
|
|
48
|
+
ba.apply_defaults()
|
|
49
|
+
|
|
50
|
+
# resolve the api path
|
|
51
|
+
path = path.format(**{k: ba.arguments[k] for k in parameters.path})
|
|
52
|
+
|
|
53
|
+
headers = filter_none_values(
|
|
54
|
+
{
|
|
55
|
+
annot.alias or param: ba.arguments[param]
|
|
56
|
+
for param, annot in parameters.headers.items()
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
params = filter_none_values(
|
|
60
|
+
{
|
|
61
|
+
annot.alias or param: ba.arguments[param]
|
|
62
|
+
for param, annot in parameters.query.items()
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
content = None
|
|
66
|
+
files = {}
|
|
67
|
+
for param, annot in parameters.body.items():
|
|
68
|
+
if (value := ba.arguments[param]) is not None:
|
|
69
|
+
if isinstance(annot, FileBody):
|
|
70
|
+
files[annot.alias or param] = annot.serialize(value)
|
|
71
|
+
else:
|
|
72
|
+
content = annot.serialize(value)
|
|
73
|
+
|
|
74
|
+
return client.build_request(
|
|
75
|
+
method, path, headers=headers, params=params, content=content, files=files
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def http(
|
|
80
|
+
path: str, method: str = "GET", response_class: Type[RESP] = Response
|
|
81
|
+
) -> Callable[[Callable], Callable[..., RESP]]:
|
|
82
|
+
def decorator(func: Callable) -> Callable[..., RESP]:
|
|
83
|
+
sig = signature(func)
|
|
84
|
+
custom_parameters = CustomParameters.from_sig(sig)
|
|
85
|
+
|
|
86
|
+
@wraps(func)
|
|
87
|
+
def wrapper(*args, **kwargs) -> RESP:
|
|
88
|
+
assert isinstance(
|
|
89
|
+
args[0], RapidApi
|
|
90
|
+
), f"{args[0]} should be an instance of RapidApi"
|
|
91
|
+
client = args[0].client
|
|
92
|
+
request = build_request(
|
|
93
|
+
client, sig, custom_parameters, method, path, args, kwargs
|
|
94
|
+
)
|
|
95
|
+
response = client.send(request)
|
|
96
|
+
if issubclass(response_class, BaseModel):
|
|
97
|
+
response.raise_for_status()
|
|
98
|
+
return response_class.model_validate_json(response.content)
|
|
99
|
+
return response
|
|
100
|
+
|
|
101
|
+
return wrapper
|
|
102
|
+
|
|
103
|
+
return decorator
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
get = partial(http, method="GET")
|
|
107
|
+
post = partial(http, method="POST")
|
|
108
|
+
delete = partial(http, method="DELETE")
|
|
109
|
+
put = partial(http, method="PUT")
|
|
110
|
+
patch = partial(http, method="PATCH")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from httpx import Client
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class RapidApi:
|
|
10
|
+
client: Client = field(default_factory=Client)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CustomParameter: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Path(CustomParameter): ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Query(CustomParameter):
|
|
21
|
+
alias: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Header(CustomParameter):
|
|
26
|
+
alias: str | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Body(CustomParameter):
|
|
30
|
+
def serialize(self, body: Any) -> str | bytes:
|
|
31
|
+
return body
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class FileBody(Body):
|
|
36
|
+
alias: str | None = None
|
|
37
|
+
|
|
38
|
+
def serialize(self, body: Any) -> str | bytes:
|
|
39
|
+
return body
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class PydanticBody(Body):
|
|
44
|
+
prettyprint: bool = False
|
|
45
|
+
|
|
46
|
+
def serialize(self, body: Any) -> str | bytes:
|
|
47
|
+
assert isinstance(body, BaseModel)
|
|
48
|
+
return body.model_dump_json(indent=2 if self.prettyprint else None)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from inspect import Parameter
|
|
2
|
+
from typing import Any, Dict, Type, TypeVar, get_args
|
|
3
|
+
|
|
4
|
+
from rapid_api_client.model import CustomParameter
|
|
5
|
+
|
|
6
|
+
CP = TypeVar("CP", bound=CustomParameter)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def filter_none_values(values: Dict[str, Any | None]) -> Dict[str, Any]:
|
|
10
|
+
return {k: v for k, v in values.items() if v is not None}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def find_annotation(param: Parameter, cls: Type[CP]) -> CP | None:
|
|
14
|
+
if param.annotation:
|
|
15
|
+
for an in get_args(param.annotation):
|
|
16
|
+
if isinstance(an, cls):
|
|
17
|
+
return an
|
|
18
|
+
return None
|