WSGI и ASGI
WSGI — синхронный интерфейс между веб-сервером и приложением: запрос занимает поток или процесс до ответа. На нём Flask и классический Django, серверы — Gunicorn, uWSGI.
ASGI — асинхронный: приложение — корутина, один процесс обслуживает тысячи соединений через цикл событий, поддерживаются WebSocket и долгие соединения. На нём FastAPI, Starlette, Django в режиме ASGI, серверы — Uvicorn, Hypercorn, Granian.
ASGI не ускоряет вычисления и бесполезен с блокирующими библиотеками. Типичный запуск — Gunicorn с воркерами Uvicorn: процессы по числу ядер, внутри каждого — свой цикл событий.
FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class ItemIn(BaseModel):
name: str
price: float
@app.get("/items/{item_id}")
async def get_item(item_id: int, full: bool = False):
item = await repo.get(item_id)
if item is None:
raise HTTPException(status_code=404)
return item
@app.post("/items", status_code=201)
async def create_item(item: ItemIn):
return await repo.create(item)
Параметр из пути становится аргументом, простые аргументы — query- параметрами, Pydantic-модель — телом запроса. Типы проверяются автоматически, ошибка — 422. Документация API — на /docs, её строят из аннотаций. response_model задаёт схему ответа и скрывает лишние поля.
Внедрение зависимостей
from typing import Annotated
from fastapi import Depends
async def get_session():
async with SessionLocal() as session:
yield session # очистка — после ответа
Session = Annotated[AsyncSession, Depends(get_session)]
@app.get("/orders")
async def list_orders(session: Session): ...
FastAPI вызывает зависимость перед обработчиком и передаёт результат. Зависимости образуют граф, результат кэшируется в пределах запроса, а код после yield выполняется после ответа. В тестах их подменяют через app.dependency_overrides. Идея та же, что у фикстур pytest.
HTTP-клиенты
requests — синхронный, httpx — похожий интерфейс плюс асинхронный клиент и HTTP/2. Обязательные привычки:
with httpx.Client(timeout=5) as client: # всегда таймаут
resp = client.get(url)
resp.raise_for_status() # 4xx и 5xx — исключение
data = resp.json()
- таймаут всегда — requests по умолчанию ждёт бесконечно и может повесить воркер;
- клиент или сессия на много запросов — переиспользуются соединения;
- повторы — только для безопасных запросов и временных ошибок;
- в асинхронном коде
requestsблокирует цикл — нуженhttpx.AsyncClient.
JSON
json.dumps и json.loads знают только dict, list, str, int, float, bool и None. Кортеж станет списком, ключи — строками. Даты, Decimal, UUID и свои классы нужно преобразовать — параметром default или через Pydantic:
json.dumps(data, default=str, ensure_ascii=False)
Даты передают в ISO 8601 (dt.isoformat()), деньги — строкой или целыми копейками, а не float. ensure_ascii=False оставляет кириллицу читаемой.
N+1 в Django
for order in Order.objects.all():
print(order.customer.name) # запрос на каждый заказ
Один запрос за списком и по запросу на каждый элемент. Лечение:
select_related("customer")— JOIN в том же запросе для ForeignKey и OneToOne;prefetch_related("items")— отдельный запрос и склейка в Python для ManyToMany и обратных связей.
Найти N+1 помогают django-debug-toolbar, лог SQL и assertNumQueries в тестах. Агрегаты считают в базе — annotate(Count(...)), а не len() в цикле.
Что запомнить
- WSGI — поток на запрос, ASGI — цикл событий и WebSocket.
- FastAPI проверяет типы сам и строит документацию из аннотаций.
- HTTP-запрос — всегда с таймаутом и проверкой статуса.
- Даты в JSON — ISO 8601, деньги — не float.
- N+1 лечат select_related и prefetch_related.