From dc852e15413bc06e77fb530f07fefc1f65cb7043 Mon Sep 17 00:00:00 2001 From: Jelmer Draaijer Date: Sun, 26 May 2024 13:07:36 +0200 Subject: [PATCH] Explain how common use cases are handled --- docs/index.md | 1 + docs/recipes.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/usage.md | 4 +- 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 docs/recipes.md diff --git a/docs/index.md b/docs/index.md index 81be695..d683e5e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,6 +15,7 @@ maxdepth: 1 --- usage +recipes reference contributing Code of Conduct diff --git a/docs/recipes.md b/docs/recipes.md new file mode 100644 index 0000000..3fce95e --- /dev/null +++ b/docs/recipes.md @@ -0,0 +1,107 @@ +from requestmodel.typing import ResponseType + +# Recipes + +This document outlines some special cases you will encounter in the wild. + +## Headers with dashes + +In Python, and most other languages, it is not possible to use dashes in variable names. However, in HTTP headers, +dashes +are used by convention. + +This can be handled in two ways + +1. `convert_underscores` which defaults to true will convert underscores to dashes in the headers. This is default + behavior so you will need to set it to false if you want to keep the underscores. +2. Another escape hatch is to use the `alias` keyword. + +## Embed keys in request body + +By design `RequestModel` will ignore the outer key in the request body. This is done to map a pydantic `BaseModel` to +the top level +of the request body. If you want to include the outer key, you can use the `embed` variable in the Body parameter. + +```python +from typing import Annotated + +from pydantic import BaseModel +from requestmodel import RequestModel +from requestmodel.params import Body + + +class PersonForm(BaseModel): + name: str + age: int + + +class PersonFormRequest(RequestModel[PersonForm]): + method = "POST" + url = "/api/v1/person/create" + # a BaseModel will be added to the body by default + body: PersonForm + # so this is the same as above + body: Annotated[PersonForm, Body()] + # remember to set the response_model to the object instead of annotating its type + response_model = PersonForm +``` + +In the above example the name and age paremeters will be sent as the top level of the request body. + +```json +{ + "name": "John", + "age": 42 +} +``` + +If you want to include the outer key, you can use the `embed` variable in the Body parameter. + +```python + body: Annotated[Form, Body(embed=True)] +``` + +Age and name will not be nested in the body key + +```json +{ + "body": { + "name": "John", + "age": 42 + } +} +``` + +## Python types as request and responses + +Most API's will return a dictionary to embed a list response behind a key to provide more meta information, in example for providing information about the pagination object. + +However, sometimes you will encounter API's that return a list directly. To enable serialization of these responses you need to make use of a TypeAdapter provided by pydantic. + +```python +from pydantic import BaseModel +from pydantic import TypeAdapter +from requestmodel import IteratorRequestModel + +class Person(BaseModel): + name: str + age: int + + +PersonList = TypeAdapter[list[Person]] + +# as the generic type, use the plain python type +class PersonRequest(IteratorRequestModel[list[Person]]): + method = "GET" + url = "/api/v1/persons" + limit: int = 10 + offset: int = 0 + + # to make serialization possible, use the TypeAdapter as the response_model + response_model = PersonList + + def next_from_response(self, response: list[Person]) -> bool: + self.offset += self.limit + return len(response) >= self.limit + +``` diff --git a/docs/usage.md b/docs/usage.md index a906408..e065206 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -20,7 +20,7 @@ class MyResponse(BaseModel): # the goal is to follow the rules FastAPI uses to describe endpoints class MyRequest(RequestModel[MyResponse]): method = "GET" - url = "https://example.com/api/v1/my-endpoint/{param1}" + url = "/api/v1/my-endpoint/{param1}" param1: str # will be used in the url param2: int # will be converted as a query parameter @@ -33,7 +33,7 @@ class MyRequest(RequestModel[MyResponse]): response_model = MyResponse -client = Client() +client = Client(base_url="https://example.com") response = MyRequest(param1="foo", param2=42).send(client)