Skip to content

Commit

Permalink
Merge pull request #81 from foarsitter/recipes
Browse files Browse the repository at this point in the history
Explain how common use cases are handled
  • Loading branch information
foarsitter committed May 27, 2024
2 parents 89a013d + dc852e1 commit 0715951
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ maxdepth: 1
---
usage
recipes
reference
contributing
Code of Conduct <codeofconduct>
Expand Down
107 changes: 107 additions & 0 deletions docs/recipes.md
Original file line number Diff line number Diff line change
@@ -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

```
4 changes: 2 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down

0 comments on commit 0715951

Please sign in to comment.