rapid-api-client 0.7.0__tar.gz → 0.7.2__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.7.0 → rapid_api_client-0.7.2}/PKG-INFO +65 -62
- {rapid_api_client-0.7.0 → rapid_api_client-0.7.2}/README.md +64 -61
- {rapid_api_client-0.7.0 → rapid_api_client-0.7.2}/pyproject.toml +1 -1
- {rapid_api_client-0.7.0 → rapid_api_client-0.7.2}/rapid_api_client/__init__.py +32 -16
- {rapid_api_client-0.7.0 → rapid_api_client-0.7.2}/rapid_api_client/client.py +4 -8
- {rapid_api_client-0.7.0 → rapid_api_client-0.7.2}/rapid_api_client/decorator.py +55 -3
- {rapid_api_client-0.7.0 → rapid_api_client-0.7.2}/LICENSE +0 -0
- {rapid_api_client-0.7.0 → rapid_api_client-0.7.2}/rapid_api_client/annotations.py +0 -0
- {rapid_api_client-0.7.0 → rapid_api_client-0.7.2}/rapid_api_client/py.typed +0 -0
- {rapid_api_client-0.7.0 → rapid_api_client-0.7.2}/rapid_api_client/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: rapid-api-client
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
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
|
|
@@ -37,20 +37,20 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|

|
|
38
38
|

|
|
39
39
|

|
|
40
|
-

|
|
41
41
|
|
|
42
|
-
> 🙏 As a Python Backend developer, I've wasted so much time in recent years writing the same API clients over and over using [Requests](https://requests.readthedocs.io/) or [HTTPX](https://www.python-httpx.org/)
|
|
42
|
+
> 🙏 As a Python Backend developer, I've wasted so much time in recent years writing the same API clients over and over using [Requests](https://requests.readthedocs.io/) or [HTTPX](https://www.python-httpx.org/). At the same time, I could be so efficient by using [FastAPI](https://fastapi.tiangolo.com/) for API servers. I just wanted to save time for my upcoming projects, thinking that other developers might find it useful too.
|
|
43
43
|
|
|
44
|
-
# Rapid
|
|
44
|
+
# Rapid API Client
|
|
45
45
|
|
|
46
46
|
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*.
|
|
47
47
|
|
|
48
48
|
✨ Main features:
|
|
49
49
|
- ✏️ You don't write any code, you only declare the endpoints using *decorators* and *annotations*.
|
|
50
50
|
- 🪄 Pydantic validation for `Header`, `Query`, `Path` or `Body` parameters.
|
|
51
|
-
- 📤 Support Pydantic to parse and validate
|
|
52
|
-
- 📥 Also support Pydantic serialization for `Body` with `POST`-like
|
|
53
|
-
- 🏗️ Does not reimplement any low-level
|
|
51
|
+
- 📤 Support Pydantic to parse and validate responses content so your method returns a model object if the response is OK.
|
|
52
|
+
- 📥 Also support Pydantic serialization for `Body` with `POST`-like operations.
|
|
53
|
+
- 🏗️ Does not reimplement any low-level HTTP related logic (like auth, transport...), it simply uses `httpx.Client` like you are used to. *Decorators* simply build the `httpx.Request` for you.
|
|
54
54
|
- ⚡️ Support `async` operations, with `httpx.AsyncClient`.
|
|
55
55
|
|
|
56
56
|
|
|
@@ -74,7 +74,7 @@ Then, declare your API client using *decorators* and *annotations*:
|
|
|
74
74
|
```python
|
|
75
75
|
from typing import Annotated, List
|
|
76
76
|
from pydantic import BaseModel
|
|
77
|
-
from rapid_api_client import RapidApi, get, post, Path, Query, JsonBody
|
|
77
|
+
from rapid_api_client import RapidApi, get, post, Path, Query, JsonBody, rapid
|
|
78
78
|
|
|
79
79
|
# Define your data models
|
|
80
80
|
class User(BaseModel):
|
|
@@ -87,6 +87,8 @@ class CreateUserRequest(BaseModel):
|
|
|
87
87
|
email: str
|
|
88
88
|
|
|
89
89
|
# Define your API client
|
|
90
|
+
# Note: the @rapid decorator is optional, but it allows you to set default values for your constructor
|
|
91
|
+
@rapid(base_url="https://api.example.com")
|
|
90
92
|
class UserApi(RapidApi):
|
|
91
93
|
# GET request with path parameter and query parameter
|
|
92
94
|
@get("/users/{user_id}")
|
|
@@ -115,7 +117,8 @@ Finally, use your API client to interact with the API:
|
|
|
115
117
|
# Use the API client
|
|
116
118
|
if __name__ == "__main__":
|
|
117
119
|
# Initialize the API client
|
|
118
|
-
|
|
120
|
+
# Note: you don't need to pass the base URL here if you used the @rapid decorator
|
|
121
|
+
api = UserApi()
|
|
119
122
|
|
|
120
123
|
# Get a user by ID
|
|
121
124
|
user = api.get_user(123)
|
|
@@ -135,9 +138,9 @@ if __name__ == "__main__":
|
|
|
135
138
|
|
|
136
139
|
# Features
|
|
137
140
|
|
|
138
|
-
##
|
|
141
|
+
## HTTP Methods
|
|
139
142
|
|
|
140
|
-
Any HTTP method can be used with `http` decorator
|
|
143
|
+
Any HTTP method can be used with the `http` decorator:
|
|
141
144
|
|
|
142
145
|
```python
|
|
143
146
|
from rapid_api_client import RapidApi, http
|
|
@@ -155,7 +158,7 @@ class MyApi(RapidApi):
|
|
|
155
158
|
def delete(self): ...
|
|
156
159
|
```
|
|
157
160
|
|
|
158
|
-
Convenient decorators are available like `get`, `post`, `delete`, `put`, `patch
|
|
161
|
+
Convenient decorators are available like `get`, `post`, `delete`, `put`, `patch`:
|
|
159
162
|
|
|
160
163
|
```python
|
|
161
164
|
from rapid_api_client import RapidApi, get, post, delete
|
|
@@ -172,7 +175,7 @@ class MyApi(RapidApi):
|
|
|
172
175
|
def delete(self): ...
|
|
173
176
|
```
|
|
174
177
|
|
|
175
|
-
To use
|
|
178
|
+
To use your API, you just need to instantiate it with a `httpx.Client` like:
|
|
176
179
|
|
|
177
180
|
```python
|
|
178
181
|
from httpx import Client
|
|
@@ -183,12 +186,12 @@ resp.raise_for_status()
|
|
|
183
186
|
```
|
|
184
187
|
|
|
185
188
|
|
|
186
|
-
## `async`
|
|
189
|
+
## `async` Support
|
|
187
190
|
|
|
188
191
|
> ✨ Since version `0.7.0`, the same code works for synchronous and `async` methods.
|
|
189
192
|
|
|
190
193
|
You can write:
|
|
191
|
-
```
|
|
194
|
+
```python
|
|
192
195
|
class GithubIssuesApi(RapidApi):
|
|
193
196
|
|
|
194
197
|
@get("/repos/{owner}/{repo}/issues")
|
|
@@ -204,35 +207,38 @@ issues_async = await api.alist_issues("essembeh", "rapid-api-client", state="clo
|
|
|
204
207
|
# both lists are the same
|
|
205
208
|
```
|
|
206
209
|
|
|
207
|
-
*
|
|
210
|
+
*Rapid API Client* supports both `sync` and `async` methods. It will automatically choose `httpx.Client` or `httpx.AsyncClient` to build and send the HTTP request.
|
|
208
211
|
|
|
209
|
-
By default, all parameters given to `RapidApi`
|
|
212
|
+
By default, all parameters given to the `RapidApi` constructor are used to instantiate a `httpx.Client` or `httpx.AsyncClient`, depending on whether your method is `async` or not. You can provide a custom `client` or `async_client` (or both) to have more control over the clients creation:
|
|
210
213
|
|
|
211
|
-
```
|
|
214
|
+
```python
|
|
212
215
|
from httpx import Client, AsyncClient
|
|
213
216
|
|
|
214
|
-
#
|
|
215
|
-
api = GithubIssuesApi(
|
|
217
|
+
# In this example, the sync client has a timeout of 10s and the async client has a timeout of 20s
|
|
218
|
+
api = GithubIssuesApi(
|
|
219
|
+
client=Client(base_url="https://api.github.com", timeout=10),
|
|
220
|
+
async_client=AsyncClient(base_url="https://api.github.com", timeout=20)
|
|
221
|
+
)
|
|
216
222
|
|
|
217
|
-
issues_sync = api.list_issues("essembeh", "rapid-api-client", state="closed")
|
|
218
|
-
issues_async = await api.alist_issues("essembeh", "rapid-api-client", state="closed")
|
|
223
|
+
issues_sync = api.list_issues("essembeh", "rapid-api-client", state="closed") # this HTTP call has a timeout of 10s
|
|
224
|
+
issues_async = await api.alist_issues("essembeh", "rapid-api-client", state="closed") # this one has a timeout of 20s
|
|
219
225
|
```
|
|
220
226
|
|
|
221
227
|
|
|
222
228
|
|
|
223
|
-
## Response
|
|
229
|
+
## Response Parsing
|
|
224
230
|
|
|
225
|
-
By default methods return a `httpx.Response` object and the
|
|
231
|
+
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).
|
|
226
232
|
|
|
227
|
-
But you can also specify a class so that the response is parsed
|
|
233
|
+
But you can also specify a class so that the response is parsed. You can use:
|
|
228
234
|
- `httpx.Response` to get the response itself, this is the default behavior
|
|
229
235
|
- `str` to get the `response.text`
|
|
230
236
|
- `bytes` to get the `response.content`
|
|
231
|
-
- Any *Pydantic* model class (subclass of `BaseModel`), the *
|
|
232
|
-
- Any *Pydantic-xml* model class (subclass of `BaseXmlModel`), the *
|
|
233
|
-
- Any other class will try to use `TypeAdapter` to parse it (see [pydantic doc](https://docs.pydantic.dev/latest/api/type_adapter/)
|
|
237
|
+
- Any *Pydantic* model class (subclass of `BaseModel`), the *JSON* will be automatically validated
|
|
238
|
+
- Any *Pydantic-xml* model class (subclass of `BaseXmlModel`), the *XML* will be automatically validated
|
|
239
|
+
- Any other class will try to use `TypeAdapter` to parse it (see [pydantic doc](https://docs.pydantic.dev/latest/api/type_adapter/))
|
|
234
240
|
|
|
235
|
-
> Note: When the returned object is not `httpx.Response`, the `raise_for_status()` is called to ensure the
|
|
241
|
+
> Note: When the returned object is not `httpx.Response`, the `raise_for_status()` is called to ensure the HTTP response is OK before parsing the content. You can disable this behavior by setting `raise_for_status=False` in the method decorator.
|
|
236
242
|
|
|
237
243
|
```python
|
|
238
244
|
class User(BaseModel):
|
|
@@ -241,7 +247,7 @@ class User(BaseModel):
|
|
|
241
247
|
|
|
242
248
|
class MyApi(RapidApi):
|
|
243
249
|
|
|
244
|
-
# This method
|
|
250
|
+
# This method returns a httpx.Response, you can omit it, but you should add it for clarity
|
|
245
251
|
@get("/user/me")
|
|
246
252
|
def get_user_raw(self) -> Response: ...
|
|
247
253
|
|
|
@@ -251,9 +257,9 @@ class MyApi(RapidApi):
|
|
|
251
257
|
```
|
|
252
258
|
|
|
253
259
|
|
|
254
|
-
## Path
|
|
260
|
+
## Path Parameters
|
|
255
261
|
|
|
256
|
-
Like `fastapi
|
|
262
|
+
Like `fastapi`, you can use your method arguments to build the API path to call:
|
|
257
263
|
|
|
258
264
|
```python
|
|
259
265
|
class MyApi(RapidApi):
|
|
@@ -273,12 +279,11 @@ class MyApi(RapidApi):
|
|
|
273
279
|
# Path parameters with a default value using a factory
|
|
274
280
|
@get("/user/{user_id}")
|
|
275
281
|
def get_user(self, user_id: Annotated[int, Path(default_factory=lambda: 42)]): ...
|
|
276
|
-
|
|
277
282
|
```
|
|
278
283
|
|
|
279
|
-
## Query
|
|
284
|
+
## Query Parameters
|
|
280
285
|
|
|
281
|
-
You can add `query parameters` to your request using the `Query` annotation
|
|
286
|
+
You can add `query parameters` to your request using the `Query` annotation:
|
|
282
287
|
|
|
283
288
|
```python
|
|
284
289
|
class MyApi(RapidApi):
|
|
@@ -299,15 +304,15 @@ class MyApi(RapidApi):
|
|
|
299
304
|
@get("/issues")
|
|
300
305
|
def get_issues(self, sort: Annotated[str, Query(default_factory=lambda: "updated")]): ...
|
|
301
306
|
|
|
302
|
-
# Query parameter with
|
|
307
|
+
# Query parameter with an alias
|
|
303
308
|
@get("/issues")
|
|
304
309
|
def get_issues(self, my_parameter: Annotated[str, Query(alias="sort")]): ...
|
|
305
310
|
```
|
|
306
311
|
|
|
307
312
|
|
|
308
|
-
## Header
|
|
313
|
+
## Header Parameters
|
|
309
314
|
|
|
310
|
-
You can add `headers` to your request using the `Header` annotation
|
|
315
|
+
You can add `headers` to your request using the `Header` annotation:
|
|
311
316
|
|
|
312
317
|
```python
|
|
313
318
|
class MyApi(RapidApi):
|
|
@@ -328,7 +333,7 @@ class MyApi(RapidApi):
|
|
|
328
333
|
@get("/issues")
|
|
329
334
|
def get_issues(self, x_version: Annotated[str, Header(default_factory=lambda: "2024.06")]): ...
|
|
330
335
|
|
|
331
|
-
# Header parameter with
|
|
336
|
+
# Header parameter with an alias
|
|
332
337
|
@get("/issues")
|
|
333
338
|
def get_issues(self, my_parameter: Annotated[str, Header(alias="x-version")]): ...
|
|
334
339
|
|
|
@@ -337,61 +342,59 @@ class MyApi(RapidApi):
|
|
|
337
342
|
def get_issues(self): ...
|
|
338
343
|
```
|
|
339
344
|
|
|
340
|
-
## Body
|
|
345
|
+
## Body Parameters
|
|
341
346
|
|
|
342
347
|
You can send a body with your request using the `Body` annotation.
|
|
343
348
|
|
|
344
|
-
This body can be
|
|
345
|
-
-
|
|
346
|
-
-
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
+
This body can be:
|
|
350
|
+
- A *raw* object with `Body`
|
|
351
|
+
- A `dict` object with `JsonBody`
|
|
352
|
+
- A *Pydantic* object with `PydanticBody`
|
|
353
|
+
- One or more files with `FileBody`
|
|
349
354
|
|
|
350
|
-
|
|
355
|
+
```python
|
|
351
356
|
class MyApi(RapidApi):
|
|
352
357
|
|
|
353
|
-
#
|
|
358
|
+
# Send a string in request content
|
|
354
359
|
@post("/string")
|
|
355
360
|
def post_string(self, body: Annotated[str, Body()]): ...
|
|
356
361
|
|
|
357
|
-
#
|
|
362
|
+
# Send a dict in request content as JSON
|
|
358
363
|
@post("/string")
|
|
359
364
|
def post_json(self, body: Annotated[dict, JsonBody()]): ...
|
|
360
365
|
|
|
361
|
-
#
|
|
366
|
+
# Send a Pydantic model serialized as JSON
|
|
362
367
|
@post("/model")
|
|
363
368
|
def post_model(self, body: Annotated[MyPydanticClass, PydanticBody()]): ...
|
|
364
369
|
|
|
365
|
-
#
|
|
370
|
+
# Send multiple files
|
|
366
371
|
@post("/files")
|
|
367
372
|
def post_files(self, report: Annotated[bytes, FileBody()], image: Annotated[bytes, FileBody()]): ...
|
|
368
373
|
|
|
369
|
-
#
|
|
374
|
+
# Send a form
|
|
370
375
|
@post("/form")
|
|
371
376
|
def post_form(self, my_param: Annotated[str, FormBody(alias="name")], extra_fields: Annotated[Dict[str, str], FormBody()]): ...
|
|
377
|
+
```
|
|
372
378
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
## Xml Support
|
|
379
|
+
## XML Support
|
|
376
380
|
|
|
377
|
-
|
|
381
|
+
XML is also supported if you use [Pydantic-Xml](https://pydantic-xml.readthedocs.io/), either for responses (if you type your function to return a `BaseXmlModel` subclass) or for POST/PUT content with `PydanticXmlBody`.
|
|
378
382
|
|
|
379
|
-
|
|
383
|
+
```python
|
|
380
384
|
class ResponseXmlRootModel(BaseXmlModel): ...
|
|
381
385
|
|
|
382
386
|
class MyApi(RapidApi):
|
|
383
387
|
|
|
384
|
-
#
|
|
388
|
+
# Parse response XML content
|
|
385
389
|
@get("/get")
|
|
386
390
|
def get_xml(self) -> ResponseXmlRootModel: ...
|
|
387
391
|
|
|
388
|
-
#
|
|
392
|
+
# Serialize XML model automatically
|
|
389
393
|
@post("/post")
|
|
390
394
|
def post_xml(self, body: Annotated[ResponseXmlRootModel, PydanticXmlBody()]): ...
|
|
395
|
+
```
|
|
391
396
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
# Examples
|
|
397
|
+
# Examples
|
|
395
398
|
|
|
396
399
|
## Authentication and Error Handling
|
|
397
400
|
|
|
@@ -460,5 +463,5 @@ if __name__ == "__main__":
|
|
|
460
463
|
main()
|
|
461
464
|
```
|
|
462
465
|
|
|
463
|
-
See [
|
|
466
|
+
See the [examples directory](./examples/) for more examples.
|
|
464
467
|
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|

|
|
2
2
|

|
|
3
3
|

|
|
4
|
-

|
|
5
5
|
|
|
6
|
-
> 🙏 As a Python Backend developer, I've wasted so much time in recent years writing the same API clients over and over using [Requests](https://requests.readthedocs.io/) or [HTTPX](https://www.python-httpx.org/)
|
|
6
|
+
> 🙏 As a Python Backend developer, I've wasted so much time in recent years writing the same API clients over and over using [Requests](https://requests.readthedocs.io/) or [HTTPX](https://www.python-httpx.org/). At the same time, I could be so efficient by using [FastAPI](https://fastapi.tiangolo.com/) for API servers. I just wanted to save time for my upcoming projects, thinking that other developers might find it useful too.
|
|
7
7
|
|
|
8
|
-
# Rapid
|
|
8
|
+
# Rapid API Client
|
|
9
9
|
|
|
10
10
|
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*.
|
|
11
11
|
|
|
12
12
|
✨ Main features:
|
|
13
13
|
- ✏️ You don't write any code, you only declare the endpoints using *decorators* and *annotations*.
|
|
14
14
|
- 🪄 Pydantic validation for `Header`, `Query`, `Path` or `Body` parameters.
|
|
15
|
-
- 📤 Support Pydantic to parse and validate
|
|
16
|
-
- 📥 Also support Pydantic serialization for `Body` with `POST`-like
|
|
17
|
-
- 🏗️ Does not reimplement any low-level
|
|
15
|
+
- 📤 Support Pydantic to parse and validate responses content so your method returns a model object if the response is OK.
|
|
16
|
+
- 📥 Also support Pydantic serialization for `Body` with `POST`-like operations.
|
|
17
|
+
- 🏗️ Does not reimplement any low-level HTTP related logic (like auth, transport...), it simply uses `httpx.Client` like you are used to. *Decorators* simply build the `httpx.Request` for you.
|
|
18
18
|
- ⚡️ Support `async` operations, with `httpx.AsyncClient`.
|
|
19
19
|
|
|
20
20
|
|
|
@@ -38,7 +38,7 @@ Then, declare your API client using *decorators* and *annotations*:
|
|
|
38
38
|
```python
|
|
39
39
|
from typing import Annotated, List
|
|
40
40
|
from pydantic import BaseModel
|
|
41
|
-
from rapid_api_client import RapidApi, get, post, Path, Query, JsonBody
|
|
41
|
+
from rapid_api_client import RapidApi, get, post, Path, Query, JsonBody, rapid
|
|
42
42
|
|
|
43
43
|
# Define your data models
|
|
44
44
|
class User(BaseModel):
|
|
@@ -51,6 +51,8 @@ class CreateUserRequest(BaseModel):
|
|
|
51
51
|
email: str
|
|
52
52
|
|
|
53
53
|
# Define your API client
|
|
54
|
+
# Note: the @rapid decorator is optional, but it allows you to set default values for your constructor
|
|
55
|
+
@rapid(base_url="https://api.example.com")
|
|
54
56
|
class UserApi(RapidApi):
|
|
55
57
|
# GET request with path parameter and query parameter
|
|
56
58
|
@get("/users/{user_id}")
|
|
@@ -79,7 +81,8 @@ Finally, use your API client to interact with the API:
|
|
|
79
81
|
# Use the API client
|
|
80
82
|
if __name__ == "__main__":
|
|
81
83
|
# Initialize the API client
|
|
82
|
-
|
|
84
|
+
# Note: you don't need to pass the base URL here if you used the @rapid decorator
|
|
85
|
+
api = UserApi()
|
|
83
86
|
|
|
84
87
|
# Get a user by ID
|
|
85
88
|
user = api.get_user(123)
|
|
@@ -99,9 +102,9 @@ if __name__ == "__main__":
|
|
|
99
102
|
|
|
100
103
|
# Features
|
|
101
104
|
|
|
102
|
-
##
|
|
105
|
+
## HTTP Methods
|
|
103
106
|
|
|
104
|
-
Any HTTP method can be used with `http` decorator
|
|
107
|
+
Any HTTP method can be used with the `http` decorator:
|
|
105
108
|
|
|
106
109
|
```python
|
|
107
110
|
from rapid_api_client import RapidApi, http
|
|
@@ -119,7 +122,7 @@ class MyApi(RapidApi):
|
|
|
119
122
|
def delete(self): ...
|
|
120
123
|
```
|
|
121
124
|
|
|
122
|
-
Convenient decorators are available like `get`, `post`, `delete`, `put`, `patch
|
|
125
|
+
Convenient decorators are available like `get`, `post`, `delete`, `put`, `patch`:
|
|
123
126
|
|
|
124
127
|
```python
|
|
125
128
|
from rapid_api_client import RapidApi, get, post, delete
|
|
@@ -136,7 +139,7 @@ class MyApi(RapidApi):
|
|
|
136
139
|
def delete(self): ...
|
|
137
140
|
```
|
|
138
141
|
|
|
139
|
-
To use
|
|
142
|
+
To use your API, you just need to instantiate it with a `httpx.Client` like:
|
|
140
143
|
|
|
141
144
|
```python
|
|
142
145
|
from httpx import Client
|
|
@@ -147,12 +150,12 @@ resp.raise_for_status()
|
|
|
147
150
|
```
|
|
148
151
|
|
|
149
152
|
|
|
150
|
-
## `async`
|
|
153
|
+
## `async` Support
|
|
151
154
|
|
|
152
155
|
> ✨ Since version `0.7.0`, the same code works for synchronous and `async` methods.
|
|
153
156
|
|
|
154
157
|
You can write:
|
|
155
|
-
```
|
|
158
|
+
```python
|
|
156
159
|
class GithubIssuesApi(RapidApi):
|
|
157
160
|
|
|
158
161
|
@get("/repos/{owner}/{repo}/issues")
|
|
@@ -168,35 +171,38 @@ issues_async = await api.alist_issues("essembeh", "rapid-api-client", state="clo
|
|
|
168
171
|
# both lists are the same
|
|
169
172
|
```
|
|
170
173
|
|
|
171
|
-
*
|
|
174
|
+
*Rapid API Client* supports both `sync` and `async` methods. It will automatically choose `httpx.Client` or `httpx.AsyncClient` to build and send the HTTP request.
|
|
172
175
|
|
|
173
|
-
By default, all parameters given to `RapidApi`
|
|
176
|
+
By default, all parameters given to the `RapidApi` constructor are used to instantiate a `httpx.Client` or `httpx.AsyncClient`, depending on whether your method is `async` or not. You can provide a custom `client` or `async_client` (or both) to have more control over the clients creation:
|
|
174
177
|
|
|
175
|
-
```
|
|
178
|
+
```python
|
|
176
179
|
from httpx import Client, AsyncClient
|
|
177
180
|
|
|
178
|
-
#
|
|
179
|
-
api = GithubIssuesApi(
|
|
181
|
+
# In this example, the sync client has a timeout of 10s and the async client has a timeout of 20s
|
|
182
|
+
api = GithubIssuesApi(
|
|
183
|
+
client=Client(base_url="https://api.github.com", timeout=10),
|
|
184
|
+
async_client=AsyncClient(base_url="https://api.github.com", timeout=20)
|
|
185
|
+
)
|
|
180
186
|
|
|
181
|
-
issues_sync = api.list_issues("essembeh", "rapid-api-client", state="closed")
|
|
182
|
-
issues_async = await api.alist_issues("essembeh", "rapid-api-client", state="closed")
|
|
187
|
+
issues_sync = api.list_issues("essembeh", "rapid-api-client", state="closed") # this HTTP call has a timeout of 10s
|
|
188
|
+
issues_async = await api.alist_issues("essembeh", "rapid-api-client", state="closed") # this one has a timeout of 20s
|
|
183
189
|
```
|
|
184
190
|
|
|
185
191
|
|
|
186
192
|
|
|
187
|
-
## Response
|
|
193
|
+
## Response Parsing
|
|
188
194
|
|
|
189
|
-
By default methods return a `httpx.Response` object and the
|
|
195
|
+
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).
|
|
190
196
|
|
|
191
|
-
But you can also specify a class so that the response is parsed
|
|
197
|
+
But you can also specify a class so that the response is parsed. You can use:
|
|
192
198
|
- `httpx.Response` to get the response itself, this is the default behavior
|
|
193
199
|
- `str` to get the `response.text`
|
|
194
200
|
- `bytes` to get the `response.content`
|
|
195
|
-
- Any *Pydantic* model class (subclass of `BaseModel`), the *
|
|
196
|
-
- Any *Pydantic-xml* model class (subclass of `BaseXmlModel`), the *
|
|
197
|
-
- Any other class will try to use `TypeAdapter` to parse it (see [pydantic doc](https://docs.pydantic.dev/latest/api/type_adapter/)
|
|
201
|
+
- Any *Pydantic* model class (subclass of `BaseModel`), the *JSON* will be automatically validated
|
|
202
|
+
- Any *Pydantic-xml* model class (subclass of `BaseXmlModel`), the *XML* will be automatically validated
|
|
203
|
+
- Any other class will try to use `TypeAdapter` to parse it (see [pydantic doc](https://docs.pydantic.dev/latest/api/type_adapter/))
|
|
198
204
|
|
|
199
|
-
> Note: When the returned object is not `httpx.Response`, the `raise_for_status()` is called to ensure the
|
|
205
|
+
> Note: When the returned object is not `httpx.Response`, the `raise_for_status()` is called to ensure the HTTP response is OK before parsing the content. You can disable this behavior by setting `raise_for_status=False` in the method decorator.
|
|
200
206
|
|
|
201
207
|
```python
|
|
202
208
|
class User(BaseModel):
|
|
@@ -205,7 +211,7 @@ class User(BaseModel):
|
|
|
205
211
|
|
|
206
212
|
class MyApi(RapidApi):
|
|
207
213
|
|
|
208
|
-
# This method
|
|
214
|
+
# This method returns a httpx.Response, you can omit it, but you should add it for clarity
|
|
209
215
|
@get("/user/me")
|
|
210
216
|
def get_user_raw(self) -> Response: ...
|
|
211
217
|
|
|
@@ -215,9 +221,9 @@ class MyApi(RapidApi):
|
|
|
215
221
|
```
|
|
216
222
|
|
|
217
223
|
|
|
218
|
-
## Path
|
|
224
|
+
## Path Parameters
|
|
219
225
|
|
|
220
|
-
Like `fastapi
|
|
226
|
+
Like `fastapi`, you can use your method arguments to build the API path to call:
|
|
221
227
|
|
|
222
228
|
```python
|
|
223
229
|
class MyApi(RapidApi):
|
|
@@ -237,12 +243,11 @@ class MyApi(RapidApi):
|
|
|
237
243
|
# Path parameters with a default value using a factory
|
|
238
244
|
@get("/user/{user_id}")
|
|
239
245
|
def get_user(self, user_id: Annotated[int, Path(default_factory=lambda: 42)]): ...
|
|
240
|
-
|
|
241
246
|
```
|
|
242
247
|
|
|
243
|
-
## Query
|
|
248
|
+
## Query Parameters
|
|
244
249
|
|
|
245
|
-
You can add `query parameters` to your request using the `Query` annotation
|
|
250
|
+
You can add `query parameters` to your request using the `Query` annotation:
|
|
246
251
|
|
|
247
252
|
```python
|
|
248
253
|
class MyApi(RapidApi):
|
|
@@ -263,15 +268,15 @@ class MyApi(RapidApi):
|
|
|
263
268
|
@get("/issues")
|
|
264
269
|
def get_issues(self, sort: Annotated[str, Query(default_factory=lambda: "updated")]): ...
|
|
265
270
|
|
|
266
|
-
# Query parameter with
|
|
271
|
+
# Query parameter with an alias
|
|
267
272
|
@get("/issues")
|
|
268
273
|
def get_issues(self, my_parameter: Annotated[str, Query(alias="sort")]): ...
|
|
269
274
|
```
|
|
270
275
|
|
|
271
276
|
|
|
272
|
-
## Header
|
|
277
|
+
## Header Parameters
|
|
273
278
|
|
|
274
|
-
You can add `headers` to your request using the `Header` annotation
|
|
279
|
+
You can add `headers` to your request using the `Header` annotation:
|
|
275
280
|
|
|
276
281
|
```python
|
|
277
282
|
class MyApi(RapidApi):
|
|
@@ -292,7 +297,7 @@ class MyApi(RapidApi):
|
|
|
292
297
|
@get("/issues")
|
|
293
298
|
def get_issues(self, x_version: Annotated[str, Header(default_factory=lambda: "2024.06")]): ...
|
|
294
299
|
|
|
295
|
-
# Header parameter with
|
|
300
|
+
# Header parameter with an alias
|
|
296
301
|
@get("/issues")
|
|
297
302
|
def get_issues(self, my_parameter: Annotated[str, Header(alias="x-version")]): ...
|
|
298
303
|
|
|
@@ -301,61 +306,59 @@ class MyApi(RapidApi):
|
|
|
301
306
|
def get_issues(self): ...
|
|
302
307
|
```
|
|
303
308
|
|
|
304
|
-
## Body
|
|
309
|
+
## Body Parameters
|
|
305
310
|
|
|
306
311
|
You can send a body with your request using the `Body` annotation.
|
|
307
312
|
|
|
308
|
-
This body can be
|
|
309
|
-
-
|
|
310
|
-
-
|
|
311
|
-
-
|
|
312
|
-
-
|
|
313
|
+
This body can be:
|
|
314
|
+
- A *raw* object with `Body`
|
|
315
|
+
- A `dict` object with `JsonBody`
|
|
316
|
+
- A *Pydantic* object with `PydanticBody`
|
|
317
|
+
- One or more files with `FileBody`
|
|
313
318
|
|
|
314
|
-
|
|
319
|
+
```python
|
|
315
320
|
class MyApi(RapidApi):
|
|
316
321
|
|
|
317
|
-
#
|
|
322
|
+
# Send a string in request content
|
|
318
323
|
@post("/string")
|
|
319
324
|
def post_string(self, body: Annotated[str, Body()]): ...
|
|
320
325
|
|
|
321
|
-
#
|
|
326
|
+
# Send a dict in request content as JSON
|
|
322
327
|
@post("/string")
|
|
323
328
|
def post_json(self, body: Annotated[dict, JsonBody()]): ...
|
|
324
329
|
|
|
325
|
-
#
|
|
330
|
+
# Send a Pydantic model serialized as JSON
|
|
326
331
|
@post("/model")
|
|
327
332
|
def post_model(self, body: Annotated[MyPydanticClass, PydanticBody()]): ...
|
|
328
333
|
|
|
329
|
-
#
|
|
334
|
+
# Send multiple files
|
|
330
335
|
@post("/files")
|
|
331
336
|
def post_files(self, report: Annotated[bytes, FileBody()], image: Annotated[bytes, FileBody()]): ...
|
|
332
337
|
|
|
333
|
-
#
|
|
338
|
+
# Send a form
|
|
334
339
|
@post("/form")
|
|
335
340
|
def post_form(self, my_param: Annotated[str, FormBody(alias="name")], extra_fields: Annotated[Dict[str, str], FormBody()]): ...
|
|
341
|
+
```
|
|
336
342
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
## Xml Support
|
|
343
|
+
## XML Support
|
|
340
344
|
|
|
341
|
-
|
|
345
|
+
XML is also supported if you use [Pydantic-Xml](https://pydantic-xml.readthedocs.io/), either for responses (if you type your function to return a `BaseXmlModel` subclass) or for POST/PUT content with `PydanticXmlBody`.
|
|
342
346
|
|
|
343
|
-
|
|
347
|
+
```python
|
|
344
348
|
class ResponseXmlRootModel(BaseXmlModel): ...
|
|
345
349
|
|
|
346
350
|
class MyApi(RapidApi):
|
|
347
351
|
|
|
348
|
-
#
|
|
352
|
+
# Parse response XML content
|
|
349
353
|
@get("/get")
|
|
350
354
|
def get_xml(self) -> ResponseXmlRootModel: ...
|
|
351
355
|
|
|
352
|
-
#
|
|
356
|
+
# Serialize XML model automatically
|
|
353
357
|
@post("/post")
|
|
354
358
|
def post_xml(self, body: Annotated[ResponseXmlRootModel, PydanticXmlBody()]): ...
|
|
359
|
+
```
|
|
355
360
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
# Examples
|
|
361
|
+
# Examples
|
|
359
362
|
|
|
360
363
|
## Authentication and Error Handling
|
|
361
364
|
|
|
@@ -424,4 +427,4 @@ if __name__ == "__main__":
|
|
|
424
427
|
main()
|
|
425
428
|
```
|
|
426
429
|
|
|
427
|
-
See [
|
|
430
|
+
See the [examples directory](./examples/) for more examples.
|
|
@@ -30,21 +30,37 @@ Basic usage:
|
|
|
30
30
|
|
|
31
31
|
from importlib.metadata import version
|
|
32
32
|
|
|
33
|
-
from .annotations import
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
from .
|
|
45
|
-
from .decorator import http
|
|
46
|
-
from .decorator import patch as patch
|
|
47
|
-
from .decorator import post as post
|
|
48
|
-
from .decorator import put as put
|
|
33
|
+
from .annotations import (
|
|
34
|
+
Body,
|
|
35
|
+
FileBody,
|
|
36
|
+
FormBody,
|
|
37
|
+
Header,
|
|
38
|
+
JsonBody,
|
|
39
|
+
Path,
|
|
40
|
+
PydanticBody,
|
|
41
|
+
PydanticXmlBody,
|
|
42
|
+
Query,
|
|
43
|
+
)
|
|
44
|
+
from .client import RapidApi
|
|
45
|
+
from .decorator import delete, get, http, patch, post, put, rapid
|
|
49
46
|
|
|
50
47
|
__version__ = version(__name__)
|
|
48
|
+
__all__ = [
|
|
49
|
+
"Body",
|
|
50
|
+
"delete",
|
|
51
|
+
"FileBody",
|
|
52
|
+
"FormBody",
|
|
53
|
+
"get",
|
|
54
|
+
"Header",
|
|
55
|
+
"http",
|
|
56
|
+
"JsonBody",
|
|
57
|
+
"patch",
|
|
58
|
+
"Path",
|
|
59
|
+
"post",
|
|
60
|
+
"put",
|
|
61
|
+
"PydanticBody",
|
|
62
|
+
"PydanticXmlBody",
|
|
63
|
+
"Query",
|
|
64
|
+
"rapid",
|
|
65
|
+
"RapidApi",
|
|
66
|
+
]
|
|
@@ -109,13 +109,9 @@ class RapidParameter(Generic[BA]):
|
|
|
109
109
|
if self.name in ba.arguments:
|
|
110
110
|
out = ba.arguments.get(self.name)
|
|
111
111
|
else:
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
elif self.annot.default_factory is not None:
|
|
116
|
-
out = self.annot.default_factory()
|
|
117
|
-
else:
|
|
118
|
-
# No default value, raise an error
|
|
112
|
+
# Use pydantic default value if available
|
|
113
|
+
out = self.annot.get_default(call_default_factory=True)
|
|
114
|
+
if out is PydanticUndefined:
|
|
119
115
|
raise ValueError(f"Missing value for parameter {self.name}")
|
|
120
116
|
|
|
121
117
|
if validate:
|
|
@@ -302,7 +298,7 @@ class ParameterManager:
|
|
|
302
298
|
# there is one PydanticBody parameter
|
|
303
299
|
if (value := first_body_param.get_value(ba)) is not None:
|
|
304
300
|
assert isinstance(value, BaseModel)
|
|
305
|
-
return "content", value.model_dump_json()
|
|
301
|
+
return "content", value.model_dump_json(by_alias=True)
|
|
306
302
|
elif isinstance(first_body_param.annot, JsonBody):
|
|
307
303
|
# there is one JsonBody parameter
|
|
308
304
|
if (value := first_body_param.get_value(ba)) is not None:
|
|
@@ -17,7 +17,7 @@ sending the requests, and processing the responses.
|
|
|
17
17
|
import inspect
|
|
18
18
|
from functools import partial, wraps
|
|
19
19
|
from inspect import signature
|
|
20
|
-
from typing import Any,
|
|
20
|
+
from typing import Any, Dict, Type
|
|
21
21
|
|
|
22
22
|
from httpx import Response
|
|
23
23
|
|
|
@@ -29,7 +29,7 @@ def http(
|
|
|
29
29
|
method: str,
|
|
30
30
|
path: str,
|
|
31
31
|
timeout: float | None = None,
|
|
32
|
-
headers:
|
|
32
|
+
headers: Dict[str, str] | None = None,
|
|
33
33
|
raise_for_status: bool = True,
|
|
34
34
|
) -> Any:
|
|
35
35
|
"""
|
|
@@ -48,6 +48,7 @@ def http(
|
|
|
48
48
|
path: The URL path template, which can contain placeholders for path parameters
|
|
49
49
|
timeout: Optional timeout for the request in seconds
|
|
50
50
|
headers: Optional additional headers to include in the request
|
|
51
|
+
raise_for_status: Whether to raise an exception for non-2xx status codes
|
|
51
52
|
|
|
52
53
|
Returns:
|
|
53
54
|
A decorator function that wraps the API endpoint method
|
|
@@ -81,7 +82,7 @@ def http(
|
|
|
81
82
|
# resolve the api path
|
|
82
83
|
resolved_path = parameter_manager.get_resolved_path(path, ba)
|
|
83
84
|
|
|
84
|
-
build_kwargs:
|
|
85
|
+
build_kwargs: Dict[str, Any] = {
|
|
85
86
|
"headers": parameter_manager.get_headers(ba),
|
|
86
87
|
"params": parameter_manager.get_query(ba),
|
|
87
88
|
}
|
|
@@ -121,6 +122,57 @@ def http(
|
|
|
121
122
|
return decorator
|
|
122
123
|
|
|
123
124
|
|
|
125
|
+
def rapid(**default_kwargs: Any) -> Any:
|
|
126
|
+
"""
|
|
127
|
+
Class decorator to configure RapidApi subclasses with default parameters.
|
|
128
|
+
|
|
129
|
+
This decorator allows specifying default parameters for RapidApi subclasses
|
|
130
|
+
that will be passed to the constructor when instantiating the class.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
**kwargs: Default arguments to pass to the RapidApi constructor
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
A decorator function that wraps the RapidApi subclass
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
>>> from rapid_api_client import RapidApi, rapid, get
|
|
140
|
+
>>> from typing import Annotated
|
|
141
|
+
>>>
|
|
142
|
+
>>> @rapid(base_url="https://api.example.com", headers={"X-API-Key": "default-key"})
|
|
143
|
+
>>> class MyApi(RapidApi):
|
|
144
|
+
... @get("/users/{user_id}")
|
|
145
|
+
... def get_user(self, user_id: Annotated[int, Path()]): ...
|
|
146
|
+
>>>
|
|
147
|
+
>>> # No need to specify base_url or headers, they're provided by the decorator
|
|
148
|
+
>>> api = MyApi()
|
|
149
|
+
>>> user = api.get_user(123)
|
|
150
|
+
>>>
|
|
151
|
+
>>> # You can still override the defaults if needed
|
|
152
|
+
>>> custom_api = MyApi(headers={"X-API-Key": "custom-key"})
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def decorator(cls: Type[RapidApi]) -> Type[RapidApi]:
|
|
156
|
+
# Store the original __init__ method
|
|
157
|
+
original_init = cls.__init__
|
|
158
|
+
|
|
159
|
+
@wraps(original_init)
|
|
160
|
+
def __init__(self: RapidApi, **init_kwargs: Any) -> None:
|
|
161
|
+
# Only apply default parameters if not present
|
|
162
|
+
for key, value in default_kwargs.items():
|
|
163
|
+
if key not in init_kwargs:
|
|
164
|
+
init_kwargs[key] = value
|
|
165
|
+
# Call the original __init__ with the merged kwargs
|
|
166
|
+
original_init(self, **init_kwargs)
|
|
167
|
+
|
|
168
|
+
# Replace the __init__ method
|
|
169
|
+
cls.__init__ = __init__ # type: ignore
|
|
170
|
+
|
|
171
|
+
return cls
|
|
172
|
+
|
|
173
|
+
return decorator
|
|
174
|
+
|
|
175
|
+
|
|
124
176
|
# Convenience decorators for common HTTP methods
|
|
125
177
|
get = partial(http, "GET")
|
|
126
178
|
post = partial(http, "POST")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|