Usage#

from httpx import Client
from pydantic import BaseModel

from requestmodel import RequestModel
from requestmodel.params import Path, Query
from typing_extensions import Annotated


# define your response,
# preferably we generate it with datamodel-code-generator based on an OpenAPI spec
class MyResponse(BaseModel):
    id: int
    name: str


# describe your request
# the goal is to follow the rules FastAPI uses to describe endpoints
class MyRequest(RequestModel[MyResponse]):
    method = "GET"
    url = "/api/v1/my-endpoint/{param1}"

    param1: str  # will be used in the url
    param2: int  # will be converted as a query parameter

    # above is the same as
    param1: Annotated[str, Path()]  # will be used in the url
    param2: Annotated[int, Query()]  # will be converted as a query parameter

    # the generic type should match with response_model so we can use it to deserialize the request
    response_model = MyResponse


client = Client(base_url="https://example.com")

response = MyRequest(param1="foo", param2=42).send(client)

assert isinstance(response, MyResponse)
assert response.name
assert response.id

There is also an IterableRequestModel that can be used for paginated responses by implementing IteratorRequestModel.next()

from typing import List, Optional

from httpx import Client
from pydantic import BaseModel

from requestmodel import IteratorRequestModel


class MyResponse(BaseModel):
    id: int
    name: str


class PaginatedMyResponse(BaseModel):
    results: List[MyResponse]
    next: Optional[str]


class MyPaginatedRequest(IteratorRequestModel[PaginatedMyResponse]):
    method = "GET"
    url = "https://example.com/api/v1/my-endpoint/{param1}"

    param1: str  # will be used in the url
    param2: int  # will be converted as a query parameter

    next: Optional[str] = None  # will be used to paginate

    # the generic type should match with response_model so we can use it to deserialize the request
    response_model = PaginatedMyResponse

    def next_from_response(self, response: PaginatedMyResponse) -> bool:
        # update the new request arguments
        self.next = response.next
        return self.next is not None


client = Client()

request = MyPaginatedRequest(param1="foo", param2=42)

for response in request.send(client):
    assert isinstance(response, MyResponse)
    assert response.name
    assert response.id