14. FastAPI: основы

Почему FastAPI?
Заголовок раздела «Почему FastAPI?»FastAPI — один из самых быстрых Python фреймворков. Сравнение с конкурентами:
| Фреймворк | Производительность | DX | Типизация |
|---|---|---|---|
| FastAPI | ⚡⚡⚡ (равен Go/Node) | ⭐⭐⭐ | Нативная |
| Flask | ⚡⚡ | ⭐⭐ | Нет |
| Django | ⚡ | ⭐⭐⭐ | Частичная |
Плюсы FastAPI:
- Автоматическая документация (Swagger UI из коробки)
- Валидация через Pydantic — пишешь типы, получаешь валидацию
- Async нативно — полная поддержка async/await
- Зависимости (DI) — мощная система внедрения зависимостей
- OpenAPI — автогенерация схемы
Установка и запуск
Заголовок раздела «Установка и запуск»pip install fastapi uvicorn
# Или с uv (быстрее):uv add fastapi uvicornfrom fastapi import FastAPI
app = FastAPI( title="My API", description="Описание API", version="1.0.0")
@app.get("/")def root(): return {"message": "Hello, World!"}# Запуск с hot-reloaduvicorn main:app --reload
# Продакшнuvicorn main:app --host 0.0.0.0 --port 8000 --workers 4Открой: http://localhost:8000/docs — интерактивная документация!
Маршруты (Routes)
Заголовок раздела «Маршруты (Routes)»from fastapi import FastAPI
app = FastAPI()
# GET@app.get("/users")def get_users(): return [{"id": 1, "name": "Яша"}]
# POST@app.post("/users")def create_user(user: dict): return user
# PUT@app.put("/users/{user_id}")def update_user(user_id: int, user: dict): return {"id": user_id, **user}
# PATCH@app.patch("/users/{user_id}")def partial_update(user_id: int, data: dict): return {"id": user_id, "updated": data}
# DELETE@app.delete("/users/{user_id}")def delete_user(user_id: int): return {"deleted": user_id}Параметры пути и запроса
Заголовок раздела «Параметры пути и запроса»from fastapi import FastAPIfrom typing import Optional
app = FastAPI()
# Path параметры@app.get("/users/{user_id}")def get_user(user_id: int): # автоматически конвертируется в int! return {"id": user_id}
@app.get("/items/{item_id}/reviews/{review_id}")def get_review(item_id: int, review_id: int): return {"item_id": item_id, "review_id": review_id}
# Query параметры (?page=1&size=10&search=python)@app.get("/items")def get_items( page: int = 1, size: int = 10, search: Optional[str] = None, # опциональный category: str | None = None, # Python 3.10+): return { "page": page, "size": size, "search": search, "category": category }
# URL: /items?page=2&size=20&search=python&category=booksPydantic модели — тело запроса
Заголовок раздела «Pydantic модели — тело запроса»from fastapi import FastAPIfrom pydantic import BaseModel, EmailStr, Fieldfrom datetime import datetimefrom typing import Optional
app = FastAPI()
class UserCreate(BaseModel): name: str = Field(min_length=1, max_length=50, description="Имя пользователя") email: EmailStr age: int = Field(ge=0, le=150) role: str = "user"
class UserResponse(BaseModel): id: int name: str email: str role: str created_at: datetime
class UserUpdate(BaseModel): name: Optional[str] = Field(None, min_length=1, max_length=50) age: Optional[int] = Field(None, ge=0, le=150)
# Используем модели@app.post("/users", response_model=UserResponse, status_code=201)def create_user(user: UserCreate): # Валидация происходит автоматически! # Если данные неверные — FastAPI вернёт 422 с описанием ошибок return { "id": 1, "name": user.name, "email": user.email, "role": user.role, "created_at": datetime.now() }
@app.patch("/users/{user_id}", response_model=UserResponse)def update_user(user_id: int, update: UserUpdate): # Обновляем только переданные поля return { "id": user_id, "name": update.name or "Яша", "role": "user", "created_at": datetime.now() }Заголовки и куки
Заголовок раздела «Заголовки и куки»from fastapi import Header, Cookie, Responsefrom typing import Annotated
@app.get("/profile")def get_profile( authorization: Annotated[str | None, Header()] = None, session_id: Annotated[str | None, Cookie()] = None,): return { "auth": authorization, "session": session_id }
@app.post("/login")def login(response: Response, username: str, password: str): # Устанавливаем куку response.set_cookie(key="session_id", value="abc123", httponly=True) return {"message": "Logged in"}HTTP статус коды
Заголовок раздела «HTTP статус коды»from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.post("/items", status_code=status.HTTP_201_CREATED)def create_item(item: dict): return item
@app.get("/items/{item_id}")def get_item(item_id: int): items = {1: "Apple", 2: "Banana"} if item_id not in items: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Item {item_id} not found" ) return {"id": item_id, "name": items[item_id]}
# Кастомные заголовки в исключении@app.get("/secure")def secure_endpoint(token: str): if token != "secret": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token", headers={"WWW-Authenticate": "Bearer"} ) return {"data": "secret data"}Структура FastAPI проекта
Заголовок раздела «Структура FastAPI проекта»app/├── main.py # точка входа├── core/│ ├── config.py # настройки│ └── database.py # подключение к БД├── models/│ └── user.py # Pydantic и DB модели├── routers/│ ├── users.py # /users эндпоинты│ └── products.py # /products эндпоинты├── services/│ └── user_service.py # бизнес-логика└── dependencies.py # FastAPI dependenciesfrom fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")def get_users(): return []
@router.get("/{user_id}")def get_user(user_id: int): return {"id": user_id}
# main.pyfrom fastapi import FastAPIfrom routers import users, products
app = FastAPI()app.include_router(users.router)app.include_router(products.router)Задание
Заголовок раздела «Задание»# Создай простое TODO API:## Модели:# - TodoCreate: title (str), description (str | None), priority (int = 1)# - TodoUpdate: title (str | None), description (str | None), done (bool | None)# - TodoResponse: id, title, description, priority, done, created_at## Endpoints:# GET /todos - список (query: page, size, done: bool | None)# POST /todos - создать# GET /todos/{id} - получить# PATCH /todos/{id} - обновить# DELETE /todos/{id} - удалить# POST /todos/{id}/done - отметить выполненным## Хранить данные в памяти (список/словарь)В следующем уроке — FastAPI CRUD с базой данных!