rapid-api-client 0.2.0__tar.gz → 0.3.1__tar.gz

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