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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rapid-api-client
3
- Version: 0.7.0
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
  ![Github](https://img.shields.io/github/tag/essembeh/rapid-api-client.svg)
38
38
  ![PyPi](https://img.shields.io/pypi/v/rapid-api-client.svg)
39
39
  ![Python](https://img.shields.io/pypi/pyversions/rapid-api-client.svg)
40
- ![CI](https://github.com/essembeh/python-helloworld/actions/workflows/poetry.yml/badge.svg)
40
+ ![CI](https://github.com/essembeh/rapid-api-client/actions/workflows/poetry.yml/badge.svg)
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/); 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.
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 Api Client
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 reponses content so your method returns a model object if the response is OK.
52
- - 📥 Also support Pydantic serialization for `Body` with `POST`-like opeations.
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.
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
- api = UserApi(base_url="https://api.example.com")
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
- ## Http methods
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 you API, you just need to instanciate it with a `httpx.Client` like:
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` support
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
- ```py
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
- *RapidApiClient* support both `sync` and `async` methods, it will automatically choose `httpx.Client` or `httpx.AsyncClient` to build and send the HTTP request.
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` contructor are used to instanciate a `httpx.Client` or `httpx.AsyncClient`, depending if your method is `async` or not, but you can provide a custom `client` or `async_client` (or both) to have more control on the clients creation.
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
- ```py
214
+ ```python
212
215
  from httpx import Client, AsyncClient
213
216
 
214
- # in this example, the sync client has a timeout of 10s and the async client has a timeout of 20s
215
- api = GithubIssuesApi(client=Client(base_url="https://api.github.com", timeout=10), async_client=AsyncClient(base_url="https://api.github.com", timeout=20))
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") # this http call has a timeout of 10s
218
- issues_async = await api.alist_issues("essembeh", "rapid-api-client", state="closed") # this one has a timeout of 20s
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 parsing
229
+ ## Response Parsing
224
230
 
225
- 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).
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, you can use:
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 *json* will be automatically validated
232
- - Any *Pydantic-xml* model class (subclass of `BaseXmlModel`), the *xml* will be automatically validated
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 http response is OK before parsing the content, you can disable this behavior by setting `raise_for_status=False` in the method decorator.
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 return a httpx.Response, you can omit it, but you should add it for clarity
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 parameters
260
+ ## Path Parameters
255
261
 
256
- Like `fastapi` you can use your method arguments to build the api path to call.
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 parameters
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 a default value
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 parameter
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 a default value
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 parameter
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
- - a *raw* object with `Body`
346
- - a `dict` object with `JsonBody`
347
- - a *Pydantic* object with `PydanticBody`
348
- - one or more files with `FileBody`
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
- ```python
355
+ ```python
351
356
  class MyApi(RapidApi):
352
357
 
353
- # send a string in request content
358
+ # Send a string in request content
354
359
  @post("/string")
355
360
  def post_string(self, body: Annotated[str, Body()]): ...
356
361
 
357
- # send a dict in request content as json
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
- # send a Pydantic model in serialized as json
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
- # send a multiple files
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
- # send a form
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
- Xml is also supported is 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`.
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
- ```python
383
+ ```python
380
384
  class ResponseXmlRootModel(BaseXmlModel): ...
381
385
 
382
386
  class MyApi(RapidApi):
383
387
 
384
- # parse response xml content
388
+ # Parse response XML content
385
389
  @get("/get")
386
390
  def get_xml(self) -> ResponseXmlRootModel: ...
387
391
 
388
- # serialize xml model automatically
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 [example directory](./examples/) for more examples
466
+ See the [examples directory](./examples/) for more examples.
464
467
 
@@ -1,20 +1,20 @@
1
1
  ![Github](https://img.shields.io/github/tag/essembeh/rapid-api-client.svg)
2
2
  ![PyPi](https://img.shields.io/pypi/v/rapid-api-client.svg)
3
3
  ![Python](https://img.shields.io/pypi/pyversions/rapid-api-client.svg)
4
- ![CI](https://github.com/essembeh/python-helloworld/actions/workflows/poetry.yml/badge.svg)
4
+ ![CI](https://github.com/essembeh/rapid-api-client/actions/workflows/poetry.yml/badge.svg)
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/); 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.
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 Api Client
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 reponses content so your method returns a model object if the response is OK.
16
- - 📥 Also support Pydantic serialization for `Body` with `POST`-like opeations.
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.
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
- api = UserApi(base_url="https://api.example.com")
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
- ## Http methods
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 you API, you just need to instanciate it with a `httpx.Client` like:
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` support
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
- ```py
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
- *RapidApiClient* support both `sync` and `async` methods, it will automatically choose `httpx.Client` or `httpx.AsyncClient` to build and send the HTTP request.
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` contructor are used to instanciate a `httpx.Client` or `httpx.AsyncClient`, depending if your method is `async` or not, but you can provide a custom `client` or `async_client` (or both) to have more control on the clients creation.
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
- ```py
178
+ ```python
176
179
  from httpx import Client, AsyncClient
177
180
 
178
- # in this example, the sync client has a timeout of 10s and the async client has a timeout of 20s
179
- api = GithubIssuesApi(client=Client(base_url="https://api.github.com", timeout=10), async_client=AsyncClient(base_url="https://api.github.com", timeout=20))
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") # this http call has a timeout of 10s
182
- issues_async = await api.alist_issues("essembeh", "rapid-api-client", state="closed") # this one has a timeout of 20s
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 parsing
193
+ ## Response Parsing
188
194
 
189
- 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).
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, you can use:
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 *json* will be automatically validated
196
- - Any *Pydantic-xml* model class (subclass of `BaseXmlModel`), the *xml* will be automatically validated
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 http response is OK before parsing the content, you can disable this behavior by setting `raise_for_status=False` in the method decorator.
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 return a httpx.Response, you can omit it, but you should add it for clarity
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 parameters
224
+ ## Path Parameters
219
225
 
220
- Like `fastapi` you can use your method arguments to build the api path to call.
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 parameters
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 a default value
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 parameter
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 a default value
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 parameter
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
- - a *raw* object with `Body`
310
- - a `dict` object with `JsonBody`
311
- - a *Pydantic* object with `PydanticBody`
312
- - one or more files with `FileBody`
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
- ```python
319
+ ```python
315
320
  class MyApi(RapidApi):
316
321
 
317
- # send a string in request content
322
+ # Send a string in request content
318
323
  @post("/string")
319
324
  def post_string(self, body: Annotated[str, Body()]): ...
320
325
 
321
- # send a dict in request content as json
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
- # send a Pydantic model in serialized as json
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
- # send a multiple files
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
- # send a form
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
- Xml is also supported is 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`.
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
- ```python
347
+ ```python
344
348
  class ResponseXmlRootModel(BaseXmlModel): ...
345
349
 
346
350
  class MyApi(RapidApi):
347
351
 
348
- # parse response xml content
352
+ # Parse response XML content
349
353
  @get("/get")
350
354
  def get_xml(self) -> ResponseXmlRootModel: ...
351
355
 
352
- # serialize xml model automatically
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 [example directory](./examples/) for more examples
430
+ See the [examples directory](./examples/) for more examples.
@@ -3,7 +3,7 @@ name = "rapid-api-client"
3
3
 
4
4
  [tool.poetry]
5
5
  name = "rapid-api-client"
6
- version = "0.7.0"
6
+ version = "0.7.2"
7
7
  description = "Rapidly develop your API clients using decorators and annotations"
8
8
  authors = ["Sébastien MB <seb@essembeh.org>"]
9
9
  readme = "README.md"
@@ -30,21 +30,37 @@ Basic usage:
30
30
 
31
31
  from importlib.metadata import version
32
32
 
33
- from .annotations import Body as Body
34
- from .annotations import FileBody as FileBody
35
- from .annotations import FormBody as FormBody
36
- from .annotations import Header as Header
37
- from .annotations import JsonBody as JsonBody
38
- from .annotations import Path as Path
39
- from .annotations import PydanticBody as PydanticBody
40
- from .annotations import PydanticXmlBody as PydanticXmlBody
41
- from .annotations import Query as Query
42
- from .client import RapidApi as RapidApi
43
- from .decorator import delete as delete
44
- from .decorator import get as get
45
- from .decorator import http as 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
- # check if pydantic model has a default value or a default factory
113
- if self.annot.default is not PydanticUndefined:
114
- out = self.annot.default
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, Mapping
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: Mapping[str, str] | None = None,
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: Mapping[str, Any] = {
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")