Загрузить файлы в «venv/Lib/site-packages/fastapi/.agents/skills/fastapi/references»
This commit is contained in:
@@ -0,0 +1,142 @@
|
|||||||
|
# Dependency Injection
|
||||||
|
|
||||||
|
Use dependencies when:
|
||||||
|
|
||||||
|
* They can't be declared in Pydantic validation and require additional logic
|
||||||
|
* The logic depends on external resources or could block in any other way
|
||||||
|
* Other dependencies need their results (it's a sub-dependency)
|
||||||
|
* The logic can be shared by multiple endpoints to do things like error early, handle authentication, etc.
|
||||||
|
* They need to handle cleanup (e.g., DB sessions, file handles), using dependencies with `yield`
|
||||||
|
* Their logic needs input data from the request, like headers, query parameters, etc.
|
||||||
|
|
||||||
|
## Dependencies with `yield` and `scope`
|
||||||
|
|
||||||
|
When using dependencies with `yield`, they can have a `scope` that defines when the exit code is run.
|
||||||
|
|
||||||
|
Use the default scope `"request"` to run the exit code after the response is sent back.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends, FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
db = DBSession()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
DBDep = Annotated[DBSession, Depends(get_db)]
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/items/")
|
||||||
|
async def read_items(db: DBDep):
|
||||||
|
return db.query(Item).all()
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the scope `"function"` when they should run the exit code after the response data is generated but before the response is sent back to the client.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends, FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
def get_username():
|
||||||
|
try:
|
||||||
|
yield "Rick"
|
||||||
|
finally:
|
||||||
|
print("Clean up before response is sent")
|
||||||
|
|
||||||
|
UserNameDep = Annotated[str, Depends(get_username, scope="function")]
|
||||||
|
|
||||||
|
@app.get("/users/me")
|
||||||
|
def get_user_me(username: UserNameDep):
|
||||||
|
return username
|
||||||
|
```
|
||||||
|
|
||||||
|
## Class Dependencies
|
||||||
|
|
||||||
|
Avoid creating class dependencies when possible.
|
||||||
|
|
||||||
|
If a class is needed, instead create a regular function dependency that returns a class instance.
|
||||||
|
|
||||||
|
Do this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends, FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DatabasePaginator:
|
||||||
|
offset: int = 0
|
||||||
|
limit: int = 100
|
||||||
|
q: str | None = None
|
||||||
|
|
||||||
|
def get_page(self) -> dict:
|
||||||
|
# Simulate a page of data
|
||||||
|
return {
|
||||||
|
"offset": self.offset,
|
||||||
|
"limit": self.limit,
|
||||||
|
"q": self.q,
|
||||||
|
"items": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_paginator(
|
||||||
|
offset: int = 0, limit: int = 100, q: str | None = None
|
||||||
|
) -> DatabasePaginator:
|
||||||
|
return DatabasePaginator(offset=offset, limit=limit, q=q)
|
||||||
|
|
||||||
|
|
||||||
|
PaginatorDep = Annotated[DatabasePaginator, Depends(get_db_paginator)]
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/items/")
|
||||||
|
async def read_items(paginator: PaginatorDep):
|
||||||
|
return paginator.get_page()
|
||||||
|
```
|
||||||
|
|
||||||
|
instead of this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# DO NOT DO THIS
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends, FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class DatabasePaginator:
|
||||||
|
def __init__(self, offset: int = 0, limit: int = 100, q: str | None = None):
|
||||||
|
self.offset = offset
|
||||||
|
self.limit = limit
|
||||||
|
self.q = q
|
||||||
|
|
||||||
|
def get_page(self) -> dict:
|
||||||
|
# Simulate a page of data
|
||||||
|
return {
|
||||||
|
"offset": self.offset,
|
||||||
|
"limit": self.limit,
|
||||||
|
"q": self.q,
|
||||||
|
"items": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/items/")
|
||||||
|
async def read_items(paginator: Annotated[DatabasePaginator, Depends()]):
|
||||||
|
return paginator.get_page()
|
||||||
|
```
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# Other Tools
|
||||||
|
|
||||||
|
## uv
|
||||||
|
|
||||||
|
If uv is available, use it to manage dependencies.
|
||||||
|
|
||||||
|
## Ruff
|
||||||
|
|
||||||
|
If Ruff is available, use it to lint and format the code. Consider enabling the FastAPI rules.
|
||||||
|
|
||||||
|
## ty
|
||||||
|
|
||||||
|
If ty is available, use it to check types.
|
||||||
|
|
||||||
|
## Asyncer
|
||||||
|
|
||||||
|
When needing to run blocking code inside of async functions, or async code inside of blocking functions, suggest using Asyncer.
|
||||||
|
|
||||||
|
Prefer it over AnyIO or asyncio.
|
||||||
|
|
||||||
|
Install:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv add asyncer
|
||||||
|
```
|
||||||
|
|
||||||
|
Run blocking sync code inside of async with `asyncify()`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from asyncer import asyncify
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
def do_blocking_work(name: str) -> str:
|
||||||
|
# Some blocking I/O operation
|
||||||
|
return f"Hello {name}"
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/items/")
|
||||||
|
async def read_items():
|
||||||
|
result = await asyncify(do_blocking_work)(name="World")
|
||||||
|
return {"message": result}
|
||||||
|
```
|
||||||
|
|
||||||
|
And run async code inside of blocking sync code with `syncify()`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from asyncer import syncify
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
async def do_async_work(name: str) -> str:
|
||||||
|
return f"Hello {name}"
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/items/")
|
||||||
|
def read_items():
|
||||||
|
result = syncify(do_async_work)(name="World")
|
||||||
|
return {"message": result}
|
||||||
|
```
|
||||||
|
|
||||||
|
## SQLModel for SQL databases
|
||||||
|
|
||||||
|
When working with SQL databases, prefer using SQLModel as it is integrated with Pydantic and will allow declaring data validation with the same models.
|
||||||
|
|
||||||
|
Prefer it over SQLAlchemy.
|
||||||
|
|
||||||
|
## HTTPX
|
||||||
|
|
||||||
|
Use HTTPX for handling HTTP communication (e.g. with other APIs). It supports sync and async usage.
|
||||||
|
|
||||||
|
Prefer it over Requests.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user