Metadata-Version: 2.1
Name: rapid-api-client
Version: 0.4.0
Summary: Rapidly develop your API clients using decorators and annotations
Author: Sébastien MB
Author-email: seb@essembeh.org
Requires-Python: >=3.11,<4.0
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: AsyncIO
Classifier: Framework :: Pydantic
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Internet
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Dist: httpx (>=0.27.2,<0.28.0)
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
Description-Content-Type: text/markdown

![Github](https://img.shields.io/github/tag/essembeh/rapid-api-client.svg)
![PyPi](https://img.shields.io/pypi/v/rapid-api-client.svg)
![Python](https://img.shields.io/pypi/pyversions/rapid-api-client.svg)
![CI](https://github.com/essembeh/python-helloworld/actions/workflows/poetry.yml/badge.svg)


# Rapid Api Client

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*.

✨ Main features:
- ✏️ You don't write any code, you only declare the endpoints using *decorators* and *annotations*.
- 🪄 Pydantic validation for `Header`, `Query`, `Path` or `Body` parameters.
- 📤 Support Pydantic to parse and validate reponses content so your method returns a model object if the response is OK.
- 📥 Also support Pydantic serialization for `Body` with `POST`-like opeations.
- 🏗️ Does not reimplement the low-level http related logic, it simply relies on `httpx.AsyncClient` like you would do and you can customize it.
- ⚡️ Asynchronous, because `httpx` and `asyncio` are just amazingly fast.

🙏 As a Python Backend developer, I've wasted so much time in recent years writing the same API clients over and over using `requests` or `httpx`; At the same time, I could be so efficient by using [FastAPI](https://fastapi.tiangolo.com/); I just wanted to save time for my upcoming projects, thinking that other developers might find it useful too.


# Usage 

Install the project

```sh
pip install rapid-api-client
```

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 🙈.

```python
class GithubIssuesApi(RapidApi):

    @get("/repos/{owner}/{repo}/issues", response_class=TypeAdapter(List[Issue]))
    async def list_issues(self, owner: Annotated[str, Path()], repo: Annotated[str, Path()]): ...

    @get("/repos/{owner}/{repo}/releases", response_class=TypeAdapter(List[Release]))
    async def list_releases(self, owner: Annotated[str, Path()], repo: Annotated[str, Path()]): ...

```

Use it 

```python
    api = GithubIssuesApi(client)
    issues = await api.list_issues("essembeh", "rapid-api-client", state="closed")
    for issue in issues:
        print(f"Issue: {issue.title} [{issue.url}]")
```

# Features

## Http method

Any HTTP method can be used with `http` decorator

```python
class MyApi(RapidApi)

    @http("GET", "/anything")
    async def get(self): ...

    @http("POST", "/anything")
    async def post(self): ...

    @http("DELETE", "/anything")
    async def delete(self): ...
```

Convenient decorators are available like `get`, `post`, `delete`, `put`, `patch`

```python
class MyApi(RapidApi)

    @get("/anything")
    async def get(self): ...

    @post("/anything")
    async def post(self): ...

    @delete("/anything")
    async def delete(self): ...
```


## Response class

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).

But you can also specify a class so that the response is parsed, you can use:
- `httpx.Response` to get the response itself, this is the default behavior
- `str` to get the `response.text` 
- `bytes` to get the `response.content` 
- Any *Pydantic* model class (subclass of `BaseModel`), the *json* will be automatically validated
- Any *Pydantic-xml* model class (subclass of `BaseXmlModel`), the *xml* will be automatically validated
- Any `TypeAdapter` to parse the *json*, see [pydantic doc](https://docs.pydantic.dev/latest/api/type_adapter/)

> 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

```python
class User(BaseModel): ...

class MyApi(RapidApi)

    # this method return a httpx.Response
    @get("/user/me")
    async def get_user_raw(self): ...

    # this method returns a User class
    @get("/user/me", response_class=User)
    async def get_user(self): ...
```


## Path parameters

Like `fastapi` you can use your method arguments to build the api path to call.

```python
class MyApi(RapidApi):

    # Path parameter
    @get("/user/{user_id}")
    async def get_user(self, user_id: Annotated[int, Path()]): ...

    # Path parameters with value validation
    @get("/user/{user_id}")
    async def get_user(self, user_id: Annotated[PositiveInt, Path()]): ...

    # Path parameters with a default value
    @get("/user/{user_id}")
    async def get_user(self, user_id: Annotated[int, Path(default=1)]): ...

    # Path parameters with a default value using a factory
    @get("/user/{user_id}")
    async def get_user(self, user_id: Annotated[int, Path(default_factory=lambda: 42)]): ...

```

## Query parameters

You can add `query parameters` to your request using the `Query` annotation.

```python
class MyApi(RapidApi):

    # Query parameter
    @get("/issues")
    async def get_issues(self, sort: Annotated[str, Query()]): ...

    # Query parameters with value validation
    @get("/issues")
    async def get_issues(self, sort: Annotated[Literal["updated", "id"], Query()]): ...

    # Query parameter with a default value
    @get("/issues")
    async def get_issues(self, sort: Annotated[str, Query(default="updated")]): ...

    # Query parameter with a default value using a factory
    @get("/issues")
    async def get_issues(self, sort: Annotated[str, Query(default_factory=lambda: "updated")]): ...

    # Query parameter with a default value
    @get("/issues")
    async def get_issues(self, my_parameter: Annotated[str, Query(alias="sort")]): ...
```


## Header parameter

You can add `headers` to your request using the `Header` annotation.

```python
class MyApi(RapidApi):

    # Header parameter
    @get("/issues")
    async def get_issues(self, x_version: Annotated[str, Header()]): ...

    # Header parameters with value validation
    @get("/issues")
    async def get_issues(self, x_version: Annotated[Literal["2024.06", "2024.01"], Header()]): ...

    # Header parameter with a default value
    @get("/issues")
    async def get_issues(self, x_version: Annotated[str, Header(default="2024.06")]): ...

    # Header parameter with a default value using a factory
    @get("/issues")
    async def get_issues(self, x_version: Annotated[str, Header(default_factory=lambda: "2024.06")]): ...

    # Header parameter with a default value
    @get("/issues")
    async def get_issues(self, my_parameter: Annotated[str, Header(alias="x-version")]): ...
```

## Body parameter

You can send a body with your request using the `Body` annotation. 

This body can be 
 - a *raw* object with `Body`
 - a *Pydantic* object  with `PydanticBody`
 - one or more files with `FileBody`

 ```python
class MyApi(RapidApi):

    # send a string in request content
    @post("/string")
    async def post_string(self, body: Annotated[str, Body()]): ...

    # send a string in request content
    @post("/model")
    async def post_model(self, body: Annotated[MyPydanticClass, PydanticBody()]): ...

    # send a multiple files
    @post("/files")
    async def post_files(self, report: Annotated[bytes, FileBody()], image: Annotated[bytes, FileBody()]): ...

    # send a form
    @post("/form")
    def post_form(self, my_param: Annotated[str, FormBody(alias="name")], extra_fields: Annotated[Dict[str, str], FormBody()]): ...

 ```

 ## Xml Support

 Xml is also supported is you use [Pydantic-Xml](https://pydantic-xml.readthedocs.io/), either for responses with `response_class` or for POST/PUT content with `PydanticXmlBody`.

 ```python
class ResponseXmlRootModel(BaseXmlModel): ...

class MyApi(RapidApi):

    # parse response xml content
    @get("/get", response_class=ResponseXmlRootModel)
    async def get_xml(self): ...

    # serialize xml model automatically
    @post("/post")
    async def post_xml(self, body: Annotated[ResponseXmlRootModel, PydanticXmlBody()]): ...

 ```


 # Examples

 See [example directory](./examples/) for some examples

