Tóm tắt: uv add "fastapi[standard]" → khai báo FastAPI(), route bằng decorator, body/response bằng Pydantic BaseModel, kiểm tra quyền bằng Depends → fastapi dev main.py → mở /docs. Ví dụ dưới đã test với FastAPI 0.115 và Pydantic 2.11.
Luồng hoạt động#
flowchart LR
C[Client] -->|HTTP JSON| U[uvicorn]
U --> F[FastAPI router]
F --> D["Depends (auth, settings)"]
F --> V["Pydantic validate body/query"]
V -->|lỗi| E[422 Unprocessable Entity]
V --> H[Handler]
H --> R["response_model lọc output"]
R --> C
Cấu hình#
Cài đặt#
uv init netinv && cd netinv
uv add "fastapi[standard]" # kèm uvicorn, fastapi-cli, httpx
Ứng dụng tối thiểu#
main.py from contextlib import asynccontextmanager
from ipaddress import IPv4Address
from typing import Annotated
from fastapi import Depends, FastAPI, Header, HTTPException, Query, status
from pydantic import BaseModel, ConfigDict, Field
DEVICES : dict[ str , dict ] = {}
@asynccontextmanager
async def lifespan (app: FastAPI):
# chạy 1 lần khi khởi động: mở DB pool, nạp cache...
DEVICES [ "pe1-hni" ] = { "hostname" : "pe1-hni" , "mgmt_ip" : "198.51.100.11" , "platform" : "iosxr" }
yield
DEVICES .clear() # khi tắt
app = FastAPI( title = "NetInventory API" , version = "0.1.0" , lifespan = lifespan)
class DeviceIn ( BaseModel ):
model_config = ConfigDict( extra = "forbid" ) # field lạ -> 422
hostname: str = Field( min_length = 1 , max_length = 63 , pattern = r " ^[a-z0-9-] + $ " )
mgmt_ip: IPv4Address
platform: str = Field( default = "iosxr" , examples = [ "iosxr" , "junos" ])
class DeviceOut ( DeviceIn ):
pass
async def require_api_key (x_api_key: Annotated[ str | None , Header()] = None ) -> str :
# Header x_api_key <-> HTTP header "X-API-Key"
if x_api_key is None :
raise HTTPException( status_code = status. HTTP_401_UNAUTHORIZED , detail = "missing X-API-Key" )
return x_api_key
AuthDep = Annotated[ str , Depends(require_api_key)]
@app.get ( "/healthz" )
async def healthz () -> dict[ str , str ]:
return { "status" : "ok" }
@app.get ( "/devices" , response_model = list[DeviceOut])
async def list_devices (
platform: Annotated[ str | None , Query( max_length = 20 )] = None ,
limit: int = 100 ,
):
items = [d for d in DEVICES .values() if platform is None or d[ "platform" ] == platform]
return items[:limit]
@app.get ( "/devices/ {hostname} " , response_model = DeviceOut)
async def get_device (hostname: str ):
if hostname not in DEVICES :
raise HTTPException( status_code = 404 , detail = "device not found" )
return DEVICES [hostname]
@app.post ( "/devices" , response_model = DeviceOut, status_code = status. HTTP_201_CREATED )
async def create_device (device: DeviceIn, _: AuthDep):
if device.hostname in DEVICES :
raise HTTPException( status_code = 409 , detail = "already exists" )
DEVICES [device.hostname] = device.model_dump( mode = "json" )
return DEVICES [device.hostname]
Quy tắc FastAPI suy ra nguồn tham số:
Khai báo
Lấy từ
Tên trùng {hostname} trong path
Path parameter
Kiểu đơn (str, int, bool...) không có trong path
Query string
Kiểu BaseModel
JSON body
Annotated[..., Header()] / Cookie() / Form()
Header / cookie / form
Annotated[..., Depends(func)]
Kết quả của func (có thể lồng nhau)
Chạy#
uv run fastapi dev main.py # dev, auto reload, cổng 8000
uv run fastapi run main.py --port 8000 # production mode, listen 0.0.0.0
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 # gọi uvicorn trực tiếp
Gọi thử và OpenAPI#
curl -s localhost:8000/devices?platform=iosxr
curl -s -X POST localhost:8000/devices \
-H "X-API-Key: $API_KEY " -H 'Content-Type: application/json' \
-d '{"hostname":"pe2-hcm","mgmt_ip":"198.51.100.12"}'
curl -s localhost:8000/openapi.json | jq '.paths | keys'
URL
Nội dung
/docs
Swagger UI, gọi thử API trên trình duyệt
/redoc
ReDoc
/openapi.json
Schema OpenAPI, dùng để sinh client
Pydantic v2: thay đổi hay gặp so với v1#
v1
v2
class Config:
model_config = ConfigDict(...)
.dict() / .json()
.model_dump() / .model_dump_json()
parse_obj()
Model.model_validate(data)
orm_mode = True
from_attributes=True
@validator
@field_validator("field") (kèm @classmethod)
Field(regex=...)
Field(pattern=...)
BaseSettings trong pydantic
Gói riêng pydantic-settings
Lỗi thường gặp#
Triệu chứng
Nguyên nhân
Cách xử lý
422 Unprocessable Entity
Body/query sai kiểu hoặc thiếu field
Đọc detail[].loc và detail[].type trong response
fastapi: command not found
Cài fastapi không kèm [standard]
uv add "fastapi[standard]" hoặc dùng uvicorn main:app
Error loading ASGI app. Could not import module "main"
Sai thư mục hoặc sai module:app
Chạy từ thư mục chứa main.py, hoặc --app-dir
API treo khi nhiều request
Gọi hàm blocking (requests, time.sleep, SSH) trong async def
Dùng def thường (chạy threadpool) hoặc thư viện async
Response lộ field nhạy cảm
Trả thẳng dict/ORM object
Khai báo response_model chỉ gồm field public
AttributeError: 'Model' object has no attribute 'dict'
Code viết cho Pydantic v1
Đổi sang model_dump()
Trình duyệt báo CORS
Frontend khác origin
Thêm CORSMiddleware với allow_origins cụ thể
Tham khảo#