Metadata-Version: 2.1
Name: lima-api
Version: 0.1.0
Summary: Lima-API is sync and async library that allows implements Rest APIs libs with python typing.
Author-email: Cesar Gonzalez <cgonzalez@paradigmadigital.com>, Victor Torre <vatorre@paradigmadigital.com>
License: MIT License
        
        Copyright (c) 2023 Paradigma Digital S.L.
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Classifier: License :: OSI Approved :: MIT License
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx
Requires-Dist: opentelemetry-instrumentation-httpx
Provides-Extra: test
Requires-Dist: coverage; extra == "test"
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Requires-Dist: pytest-mock; extra == "test"
Requires-Dist: ruff; extra == "test"
Provides-Extra: pydantic2
Requires-Dist: pydantic[email]<3.0,>=2.3.0; extra == "pydantic2"
Requires-Dist: pydantic-settings>=2.0.3; extra == "pydantic2"
Provides-Extra: pydantic1
Requires-Dist: pydantic[email]<2.0,>=1.10; extra == "pydantic1"
Provides-Extra: all
Requires-Dist: lima_api[pydantic2,test]; extra == "all"

# Lima-API
Lima-API is sync and async library that allows implements Rest APIs libs with python typing.


# Howto use it
1. Create your [Pydantic](https://docs.pydantic.dev/latest/) models.
    ```python
    from pydantic import BaseModel
    from pydantic.fields import Field

    class Pet(BaseModel):
        identifier: int = Field(alias="id")
        name: str
    ```
2. Create your exceptions extend from `lima_api.LimaException`
    ```python
    import lima_api

    class PetNotFoundError(lima_api.LimaException): ...

    class InvalidDataError(lima_api.LimaException): ...
    ```
3. Create your class extend from `lima_api.SyncLimaApi` or `lima_api.LimaApi`.
    ```python
    import lima_api
    ...

    class PetApi(lima_api.LimaApi):
        response_mapping = {
            404: PetNotFoundError,
        }
    ```
4. Create functions with the proper decorator.
    ```python
    import lima_api
    ...

    class PetApi(lima_api.LimaApi):
        ...

        @lima_api.get(
            "/pet/{petId}",
            response_mapping={
                400: InvalidDataError,
            }
        )
        async def get_pet(self, *, petId: int) -> Pet:
            ...
    ```
5. Create the client instance.
    ```python
    pet_client = PetApi("https://petstore.swagger.io/v2")
    async with pet_client:
        pet = await pet_client.get_pet(pet_id=1)
    ```


In some case you want remove APIs complexity( for example, pagination creating a private function with decorator and public one without it:
``` python
class StApi(lima_api.LimaApi):
    @lima_api.get("/v1/rest/animal/search")
    async def _animals_search(
        self,
        *,
        page_number: int = lima_api.QueryParameter(alias="pageNumber"),
        page_size: int = lima_api.QueryParameter(alias="pageSize", default=100),
    ) -> AnimalBaseResponse: ...

    async def list_animals(self) -> AsyncIterator[AnimalBase]:
        page_number = 0
        page = await self._search(page_number=page_number)
        while not page.page.lastPage:
            for animal in page.animals:
                yield animal
            page_number += 1
            page = await self._search(page_number=page_number)
        for animal in page.animals:
            yield animal
```

> [!NOTE]
> * Synchronous clients only support synchronous functions, and in the same way with asynchronous.
> * You could see other code examples at [docs/examples](docs/examples) folder.


> [!IMPORTANT]
> * The Body param must be allways `BaseModel` calls and only one is valid
> * Functions wrapped by lima_api always must use *, in order to force use keywords for calling functions.


# Parameters types
The functions parameters will mapping with the following criteria.
1. You could define the location of the param using `lima_api.LimaParameter` (one of the followings `lima_api.PathParameter`, `lima_api.QueryParameter` or `lima_api.BodyParameter`) classes.
   ```python
    from enum import Enum

    import lima_api
    from pydantic import BaseModel
    ...

    class PetUpdateStatus(BaseModel):
        name: str
        status: str
   
    class PetStatus(str, Enum):
        AVAILABLE = "available"
        PENDING = "pending"
        SOLD = "sold"

    class PetApi(lima_api.LimaApi):
        ...

        @lima_api.post(
            "/pets/{petId}",
            headers={"content-type": "application/x-www-form-urlencoded"},
            response_mapping={405: InvalidDataError}
        )
        async def get_update_pet(
            self,
            *,
            pet_id: int = lima_api.PathParameter(alias="petId"),
            data: PetUpdateStatus = lima_api.BodyParameter(),
        ) -> None: ...
    
        @lima_api.get("/pet/findByStatus")
        async def filter(
            self,
            *,
            status: list[PetStatus] = lima_api.QueryParameter(default=[]),
        ) -> list[Pet]: ...
   ```
2. The parameters that extend from `pydantic.BaseModel` will send to body by default except if the method is `GET`, in this case will send as query params.
   ```python
    from enum import Enum

    import lima_api
    from pydantic import BaseModel
    ...

    class PetStatus(str, Enum):
        AVAILABLE = "available"
        PENDING = "pending"
        SOLD = "sold"

    class PetFilterStatus(BaseModel):
        status: list[PetStatus]
    
   
    class PetApi(lima_api.LimaApi):
        ...

        @lima_api.get("/pet/findByStatus")
        async def filter(self, *, status: list[PetStatus]) -> list[Pet]: ...

        @lima_api.get("/pet/findByStatus")
        async def filter_by_obj(self, *, data: PetFilterStatus) -> list[Pet]: ...
   ```
3. At the end with the regex expressed in `LimaSettings.lima_bracket_regex` will get the names in the path params and macht the param names defined in the function.

   For example with `lima_bracket_regex = r"\[(.+?)\]"`
   ```python
    @lima_api.get("/pets/[petId]")
    async def get_pet(self, petId: str) -> Pet: ...
   ```
