committed by
GitHub
4 changed files with 328 additions and 204 deletions
@ -0,0 +1,93 @@ |
|||||
|
# Path Operations and Routing |
||||
|
|
||||
|
## Including Routers |
||||
|
|
||||
|
When declaring routers, prefer to add router-level parameters like prefix, tags, and shared dependencies to the router itself instead of in `include_router()`. |
||||
|
|
||||
|
Do this: |
||||
|
|
||||
|
```python |
||||
|
from fastapi import APIRouter, FastAPI |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
router = APIRouter(prefix="/items", tags=["items"]) |
||||
|
|
||||
|
|
||||
|
@router.get("/") |
||||
|
async def list_items(): |
||||
|
return [] |
||||
|
|
||||
|
|
||||
|
app.include_router(router) |
||||
|
``` |
||||
|
|
||||
|
Instead of: |
||||
|
|
||||
|
```python |
||||
|
# DO NOT DO THIS |
||||
|
from fastapi import APIRouter, FastAPI |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
router = APIRouter() |
||||
|
|
||||
|
|
||||
|
@router.get("/") |
||||
|
async def list_items(): |
||||
|
return [] |
||||
|
|
||||
|
|
||||
|
app.include_router(router, prefix="/items", tags=["items"]) |
||||
|
``` |
||||
|
|
||||
|
There could be exceptions, but try to follow this convention. |
||||
|
|
||||
|
Apply shared dependencies at the router level via `dependencies=[Depends(...)]`. |
||||
|
|
||||
|
## Use one HTTP operation per function |
||||
|
|
||||
|
Don't mix HTTP operations in a single function. Having one function per HTTP operation helps separate concerns and organize the code. |
||||
|
|
||||
|
Do this: |
||||
|
|
||||
|
```python |
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def list_items(): |
||||
|
return [] |
||||
|
|
||||
|
|
||||
|
@app.post("/items/") |
||||
|
async def create_item(item: Item): |
||||
|
return item |
||||
|
``` |
||||
|
|
||||
|
Instead of: |
||||
|
|
||||
|
```python |
||||
|
# DO NOT DO THIS |
||||
|
from fastapi import FastAPI, Request |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
|
||||
|
|
||||
|
@app.api_route("/items/", methods=["GET", "POST"]) |
||||
|
async def handle_items(request: Request): |
||||
|
if request.method == "GET": |
||||
|
return [] |
||||
|
``` |
||||
@ -0,0 +1,93 @@ |
|||||
|
# Pydantic |
||||
|
|
||||
|
## Do not use Ellipsis |
||||
|
|
||||
|
Do not use `...` as a default value for required parameters or model fields. It's not needed and not recommended. |
||||
|
|
||||
|
Do this, without Ellipsis (`...`): |
||||
|
|
||||
|
```python |
||||
|
from typing import Annotated |
||||
|
|
||||
|
from fastapi import FastAPI, Query |
||||
|
from pydantic import BaseModel, Field |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str | None = None |
||||
|
price: float = Field(gt=0) |
||||
|
|
||||
|
|
||||
|
@app.post("/items/") |
||||
|
async def create_item(item: Item, project_id: Annotated[int, Query()]): |
||||
|
return item |
||||
|
``` |
||||
|
|
||||
|
Instead of: |
||||
|
|
||||
|
```python |
||||
|
# DO NOT DO THIS |
||||
|
from typing import Annotated |
||||
|
|
||||
|
from fastapi import FastAPI, Query |
||||
|
from pydantic import BaseModel, Field |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str = ... |
||||
|
description: str | None = None |
||||
|
price: float = Field(..., gt=0) |
||||
|
|
||||
|
|
||||
|
@app.post("/items/") |
||||
|
async def create_item(item: Item, project_id: Annotated[int, Query(...)]): |
||||
|
return item |
||||
|
``` |
||||
|
|
||||
|
## Do not use Pydantic RootModels |
||||
|
|
||||
|
Do not use Pydantic `RootModel`; instead use regular type annotations with `Annotated` and Pydantic validation utilities. |
||||
|
|
||||
|
For example, for a list with validations: |
||||
|
|
||||
|
```python |
||||
|
from typing import Annotated |
||||
|
|
||||
|
from fastapi import Body, FastAPI |
||||
|
from pydantic import Field |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.post("/items/") |
||||
|
async def create_items(items: Annotated[list[int], Field(min_length=1), Body()]): |
||||
|
return items |
||||
|
``` |
||||
|
|
||||
|
Instead of: |
||||
|
|
||||
|
```python |
||||
|
# DO NOT DO THIS |
||||
|
from typing import Annotated |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import Field, RootModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class ItemList(RootModel[Annotated[list[int], Field(min_length=1)]]): |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
@app.post("/items/") |
||||
|
async def create_items(items: ItemList): |
||||
|
return items |
||||
|
``` |
||||
|
|
||||
|
FastAPI supports these type annotations and will create a Pydantic `TypeAdapter` for them, so types work normally without custom wrapper models. |
||||
@ -0,0 +1,79 @@ |
|||||
|
# Responses |
||||
|
|
||||
|
## Return Type or Response Model |
||||
|
|
||||
|
When possible, include a return type. It will be used to validate, filter, document, and serialize the response. |
||||
|
|
||||
|
```python |
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str | None = None |
||||
|
|
||||
|
|
||||
|
@app.get("/items/me") |
||||
|
async def get_item() -> Item: |
||||
|
return Item(name="Plumbus", description="All-purpose home device") |
||||
|
``` |
||||
|
|
||||
|
Return types or response models filter data to avoid exposing sensitive information. They also let Pydantic serialize data on the Rust side for performance. |
||||
|
|
||||
|
The return type doesn't have to be a Pydantic model. It can be a different type, like a list of integers, a dict, etc. |
||||
|
|
||||
|
## When to use `response_model` |
||||
|
|
||||
|
If the return type is not the same as the type that you want to use to validate, filter, or serialize, use the `response_model` parameter on the decorator. |
||||
|
|
||||
|
```python |
||||
|
from typing import Any |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str | None = None |
||||
|
|
||||
|
|
||||
|
@app.get("/items/me", response_model=Item) |
||||
|
async def get_item() -> Any: |
||||
|
return {"name": "Foo", "description": "A very nice Item"} |
||||
|
``` |
||||
|
|
||||
|
This is particularly useful when filtering data to expose only the public fields and avoid exposing sensitive information. |
||||
|
|
||||
|
```python |
||||
|
from typing import Any |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class InternalItem(BaseModel): |
||||
|
name: str |
||||
|
description: str | None = None |
||||
|
secret_key: str |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str | None = None |
||||
|
|
||||
|
|
||||
|
@app.get("/items/me", response_model=Item) |
||||
|
async def get_item() -> Any: |
||||
|
item = InternalItem( |
||||
|
name="Foo", description="A very nice Item", secret_key="supersecret" |
||||
|
) |
||||
|
return item |
||||
|
``` |
||||
Loading…
Reference in new issue