Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explain how common use cases are handled #81

Merged
merged 1 commit into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading