Что такое FastAPI
FastAPI — это современный высокопроизводительный веб-фреймворк для создания API на языке Python. Разработанный Себастьяном Рамиресом, этот фреймворк построен на основе стандартов OpenAPI и JSON Schema, что обеспечивает автоматическую генерацию документации и типизацию данных.
FastAPI использует новейшие возможности Python, включая аннотации типов (type hints) и асинхронное программирование, что делает его одним из самых быстрых Python-фреймворков для разработки API. По производительности он сравним с Node.js и Go, что делает его отличным выбором для высоконагруженных приложений.
Ключевые особенности FastAPI
Высокая производительность
FastAPI построен на основе Starlette для веб-части и Pydantic для валидации данных. Это обеспечивает производительность на уровне самых быстрых Python-фреймворков. Фреймворк полностью поддерживает ASGI (Asynchronous Server Gateway Interface), что позволяет обрабатывать тысячи одновременных подключений.
Автоматическая генерация документации
Одной из главных особенностей FastAPI является автоматическая генерация интерактивной документации API. Фреймворк создает документацию в формате OpenAPI (Swagger) и предоставляет два интерфейса:
- Swagger UI — доступен по адресу
/docs - ReDoc — доступен по адресу
/redoc
Полная поддержка типизации
FastAPI использует стандартные аннотации типов Python для валидации данных, сериализации и автоматической генерации документации. Это обеспечивает отличную поддержку автодополнения в IDE и снижает количество ошибок во время разработки.
Встроенная валидация данных
Благодаря интеграции с Pydantic, FastAPI автоматически валидирует входящие данные, конвертирует типы и возвращает понятные сообщения об ошибках. Это значительно упрощает разработку и повышает надежность приложений.
Асинхронность из коробки
FastAPI полностью поддерживает асинхронное программирование с использованием async/await. Это позволяет создавать высокопроизводительные приложения, способные обрабатывать множество одновременных запросов.
Установка и базовая настройка
Установка FastAPI
Для установки FastAPI и всех необходимых зависимостей используйте следующую команду:
pip install fastapi[all]
Эта команда установит FastAPI вместе с Uvicorn (ASGI сервер) и другими полезными зависимостями.
Для минимальной установки используйте:
pip install fastapi
pip install uvicorn[standard]
Создание первого приложения
Создайте файл main.py с базовым приложением:
from fastapi import FastAPI
app = FastAPI(
title="Мой FastAPI проект",
description="Это пример FastAPI приложения",
version="1.0.0"
)
@app.get("/")
async def root():
return {"message": "Добро пожаловать в FastAPI!"}
@app.get("/health")
async def health_check():
return {"status": "ok"}
Запуск приложения
Запустите сервер разработки с помощью Uvicorn:
uvicorn main:app --reload --host 0.0.0.0 --port 8000
Параметры запуска:
--reload— автоматическая перезагрузка при изменении кода--host 0.0.0.0— принимать подключения от всех IP-адресов--port 8000— порт для запуска сервера
Работа с HTTP-методами
FastAPI поддерживает все стандартные HTTP-методы через соответствующие декораторы:
GET-запросы
@app.get("/items/{item_id}")
async def get_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
@app.get("/items/")
async def get_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
POST-запросы
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
is_offer: bool = False
@app.post("/items/")
async def create_item(item: Item):
return {"item": item, "message": "Товар создан"}
PUT и PATCH-запросы
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, "item": item}
@app.patch("/items/{item_id}")
async def update_item_partial(item_id: int, item: dict):
return {"item_id": item_id, "updated_fields": item}
DELETE-запросы
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
return {"message": f"Товар {item_id} удален"}
Работа с параметрами запроса
Path-параметры
from fastapi import Path
@app.get("/items/{item_id}")
async def get_item(
item_id: int = Path(..., title="ID товара", ge=1)
):
return {"item_id": item_id}
Query-параметры
from fastapi import Query
@app.get("/items/")
async def get_items(
q: str = Query(None, min_length=3, max_length=50),
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100)
):
return {"q": q, "skip": skip, "limit": limit}
Работа с заголовками
from fastapi import Header
@app.get("/items/")
async def get_items(
user_agent: str = Header(None),
x_token: str = Header(None, alias="X-Token")
):
return {"User-Agent": user_agent, "X-Token": x_token}
Работа с cookies
from fastapi import Cookie
@app.get("/items/")
async def get_items(ads_id: str = Cookie(None)):
return {"ads_id": ads_id}
Модели данных с Pydantic
Создание базовых моделей
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
class User(BaseModel):
id: Optional[int] = None
name: str = Field(..., min_length=1, max_length=100)
email: str = Field(..., regex=r'^[\w\.-]+@[\w\.-]+\.\w+$')
age: int = Field(..., ge=0, le=150)
created_at: datetime = Field(default_factory=datetime.now)
class Config:
schema_extra = {
"example": {
"name": "Иван Иванов",
"email": "ivan@example.com",
"age": 30
}
}
Вложенные модели
from typing import List
class Address(BaseModel):
street: str
city: str
country: str
class UserWithAddress(BaseModel):
name: str
email: str
addresses: List[Address]
Модели ответов
class UserResponse(BaseModel):
id: int
name: str
email: str
created_at: datetime
@app.post("/users/", response_model=UserResponse)
async def create_user(user: User):
# Логика создания пользователя
return UserResponse(
id=1,
name=user.name,
email=user.email,
created_at=datetime.now()
)
Асинхронное программирование
Асинхронные обработчики
import asyncio
import aiohttp
@app.get("/async-data")
async def get_async_data():
async with aiohttp.ClientSession() as session:
async with session.get('https://api.example.com/data') as response:
data = await response.json()
return data
@app.get("/delayed")
async def delayed_response():
await asyncio.sleep(2)
return {"message": "Ответ через 2 секунды"}
Работа с асинхронными базами данных
import asyncpg
from databases import Database
DATABASE_URL = "postgresql://user:password@localhost/database"
database = Database(DATABASE_URL)
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
query = "SELECT * FROM users WHERE id = :user_id"
user = await database.fetch_one(query, {"user_id": user_id})
return user
Обработка ошибок
Стандартные HTTP-исключения
from fastapi import HTTPException, status
@app.get("/items/{item_id}")
async def get_item(item_id: int):
if item_id == 0:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Товар не найден"
)
return {"item_id": item_id}
Пользовательские обработчики ошибок
from fastapi import Request
from fastapi.responses import JSONResponse
class CustomException(Exception):
def __init__(self, name: str):
self.name = name
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
return JSONResponse(
status_code=418,
content={"message": f"Произошла ошибка: {exc.name}"}
)
Зависимости и внедрение зависимостей
Простые зависимости
from fastapi import Depends
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def get_items(commons: dict = Depends(common_parameters)):
return commons
Зависимости с состоянием
class DatabaseManager:
def __init__(self):
self.connection = None
async def connect(self):
# Подключение к базе данных
pass
async def disconnect(self):
# Отключение от базы данных
pass
db_manager = DatabaseManager()
async def get_db():
return db_manager
@app.get("/items/")
async def get_items(db: DatabaseManager = Depends(get_db)):
return {"database": "connected"}
Аутентификация и авторизация
OAuth2 с JWT
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
return username
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# Проверка пользователя
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": form_data.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/protected")
async def protected_route(current_user: str = Depends(get_current_user)):
return {"message": f"Hello {current_user}"}
Middleware и CORS
Добавление CORS
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "https://myapp.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Пользовательские middleware
from fastapi import Request
import time
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
Работа с базами данных
SQLAlchemy с FastAPI
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/")
async def create_user(user: User, db: Session = Depends(get_db)):
db_user = User(name=user.name, email=user.email)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
Структура проекта
Рекомендуемая структура для больших проектов
project/
├── main.py # Точка входа
├── core/
│ ├── __init__.py
│ ├── config.py # Конфигурация
│ ├── security.py # Безопасность
│ └── database.py # Подключение к БД
├── models/
│ ├── __init__.py
│ ├── user.py # Модели пользователей
│ └── item.py # Модели товаров
├── schemas/
│ ├── __init__.py
│ ├── user.py # Pydantic схемы
│ └── item.py
├── api/
│ ├── __init__.py
│ ├── deps.py # Зависимости
│ └── v1/
│ ├── __init__.py
│ ├── endpoints/
│ │ ├── __init__.py
│ │ ├── users.py
│ │ └── items.py
│ └── api.py
├── tests/
│ ├── __init__.py
│ └── test_main.py
└── requirements.txt
Использование APIRouter
from fastapi import APIRouter
router = APIRouter(
prefix="/api/v1",
tags=["items"],
responses={404: {"description": "Not found"}}
)
@router.get("/items/")
async def get_items():
return [{"item_id": 1}]
@router.get("/items/{item_id}")
async def get_item(item_id: int):
return {"item_id": item_id}
# В main.py
app.include_router(router)
Тестирование FastAPI приложений
Базовое тестирование
from fastapi.testclient import TestClient
import pytest
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Добро пожаловать в FastAPI!"}
def test_create_item():
response = client.post(
"/items/",
json={"name": "Test Item", "price": 10.5, "is_offer": True}
)
assert response.status_code == 200
assert response.json()["item"]["name"] == "Test Item"
@pytest.mark.asyncio
async def test_async_endpoint():
response = client.get("/async-data")
assert response.status_code == 200
Тестирование с базой данных
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def override_get_db():
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
@pytest.fixture
def client():
with TestClient(app) as c:
yield c
Развертывание FastAPI приложений
Использование Docker
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Продакшн с Gunicorn
pip install gunicorn
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
Настройка Nginx
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
WebSocket поддержка
from fastapi import WebSocket
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Сообщение: {data}")
Фоновые задачи
from fastapi import BackgroundTasks
def write_notification(email: str, message: str):
with open("log.txt", mode="w") as email_file:
content = f"notification for {email}: {message}"
email_file.write(content)
@app.post("/send-notification/")
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_notification, email, "some notification")
return {"message": "Notification sent in the background"}
Полный справочник методов и функций FastAPI
| Категория | Метод/Функция | Описание | Пример использования |
|---|---|---|---|
| Создание приложения | FastAPI() |
Создание экземпляра приложения | app = FastAPI(title="My API") |
app.title |
Заголовок API | app.title = "Мой API" |
|
app.description |
Описание API | app.description = "Описание API" |
|
app.version |
Версия API | app.version = "1.0.0" |
|
| HTTP методы | @app.get() |
GET запросы | @app.get("/items/") |
@app.post() |
POST запросы | @app.post("/items/") |
|
@app.put() |
PUT запросы | @app.put("/items/{id}") |
|
@app.delete() |
DELETE запросы | @app.delete("/items/{id}") |
|
@app.patch() |
PATCH запросы | @app.patch("/items/{id}") |
|
@app.head() |
HEAD запросы | @app.head("/items/") |
|
@app.options() |
OPTIONS запросы | @app.options("/items/") |
|
| Параметры | Path() |
Параметры пути | Path(..., ge=1) |
Query() |
Query параметры | Query(None, max_length=50) |
|
Body() |
Тело запроса | Body(...) |
|
Form() |
Данные формы | Form(...) |
|
File() |
Загрузка файлов | File(...) |
|
Header() |
HTTP заголовки | Header(None) |
|
Cookie() |
Cookies | Cookie(None) |
|
| Модели данных | BaseModel |
Базовая модель Pydantic | class Item(BaseModel): ... |
Field() |
Поле модели с валидацией | Field(..., min_length=1) |
|
validator() |
Пользовательский валидатор | @validator('email') |
|
| Ответы | JSONResponse |
JSON ответ | JSONResponse(content={}) |
HTMLResponse |
HTML ответ | HTMLResponse(content="<html>") |
|
PlainTextResponse |
Текстовый ответ | PlainTextResponse("text") |
|
RedirectResponse |
Перенаправление | RedirectResponse(url="/") |
|
FileResponse |
Файл в ответе | FileResponse("file.pdf") |
|
| Статус коды | status.HTTP_200_OK |
HTTP 200 | status_code=status.HTTP_200_OK |
status.HTTP_201_CREATED |
HTTP 201 | status_code=status.HTTP_201_CREATED |
|
status.HTTP_404_NOT_FOUND |
HTTP 404 | status_code=status.HTTP_404_NOT_FOUND |
|
status.HTTP_422_UNPROCESSABLE_ENTITY |
HTTP 422 | status_code=status.HTTP_422_UNPROCESSABLE_ENTITY |
|
| Исключения | HTTPException |
HTTP исключение | HTTPException(status_code=404) |
RequestValidationError |
Ошибка валидации | Автоматически обрабатывается | |
| Зависимости | Depends() |
Внедрение зависимостей | Depends(get_current_user) |
Security() |
Зависимости безопасности | Security(oauth2_scheme) |
|
| Безопасность | OAuth2PasswordBearer |
OAuth2 аутентификация | OAuth2PasswordBearer(tokenUrl="token") |
OAuth2PasswordRequestForm |
Форма логина | Depends(OAuth2PasswordRequestForm) |
|
HTTPBasic |
HTTP Basic Auth | HTTPBasic() |
|
HTTPBearer |
HTTP Bearer Auth | HTTPBearer() |
|
| Middleware | app.add_middleware() |
Добавление middleware | app.add_middleware(CORSMiddleware) |
CORSMiddleware |
CORS поддержка | CORSMiddleware(allow_origins=["*"]) |
|
@app.middleware("http") |
Пользовательский middleware | @app.middleware("http") |
|
| Роутинг | APIRouter() |
Создание роутера | router = APIRouter() |
app.include_router() |
Подключение роутера | app.include_router(router) |
|
router.prefix |
Префикс для роутера | prefix="/api/v1" |
|
router.tags |
Теги для документации | tags=["items"] |
|
| События | @app.on_event("startup") |
Событие запуска | @app.on_event("startup") |
@app.on_event("shutdown") |
Событие остановки | @app.on_event("shutdown") |
|
| WebSocket | @app.websocket() |
WebSocket соединение | @app.websocket("/ws") |
websocket.accept() |
Принятие соединения | await websocket.accept() |
|
websocket.send_text() |
Отправка текста | await websocket.send_text("Hello") |
|
websocket.receive_text() |
Получение текста | await websocket.receive_text() |
|
| Фоновые задачи | BackgroundTasks |
Фоновые задачи | BackgroundTasks |
background_tasks.add_task() |
Добавление задачи | background_tasks.add_task(func) |
|
| Тестирование | TestClient |
Тестовый клиент | TestClient(app) |
client.get() |
GET запрос в тестах | client.get("/") |
|
client.post() |
POST запрос в тестах | client.post("/", json={}) |
|
| Конфигурация | app.openapi_url |
URL для OpenAPI схемы | app.openapi_url = "/openapi.json" |
app.docs_url |
URL для Swagger UI | app.docs_url = "/docs" |
|
app.redoc_url |
URL для ReDoc | app.redoc_url = "/redoc" |
|
| Утилиты | jsonable_encoder |
Кодирование в JSON | jsonable_encoder(obj) |
create_response |
Создание ответа | create_response(content) |
Практические примеры использования
API для интернет-магазина
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI(title="Интернет-магазин API")
class Product(BaseModel):
id: Optional[int] = None
name: str
price: float
description: str
category_id: int
class Category(BaseModel):
id: Optional[int] = None
name: str
@app.get("/products/", response_model=List[Product])
async def get_products(category_id: Optional[int] = None, skip: int = 0, limit: int = 100):
# Логика получения продуктов
return []
@app.post("/products/", response_model=Product)
async def create_product(product: Product):
# Логика создания продукта
return product
@app.get("/categories/", response_model=List[Category])
async def get_categories():
# Логика получения категорий
return []
API для блога
from datetime import datetime
from typing import List
class Post(BaseModel):
id: Optional[int] = None
title: str
content: str
author_id: int
created_at: datetime = Field(default_factory=datetime.now)
published: bool = False
class Comment(BaseModel):
id: Optional[int] = None
post_id: int
author_id: int
content: str
created_at: datetime = Field(default_factory=datetime.now)
@app.get("/posts/", response_model=List[Post])
async def get_posts(published: bool = True, skip: int = 0, limit: int = 10):
return []
@app.post("/posts/", response_model=Post)
async def create_post(post: Post):
return post
@app.get("/posts/{post_id}/comments/", response_model=List[Comment])
async def get_post_comments(post_id: int):
return []
Часто задаваемые вопросы
В чем основные отличия FastAPI от Flask?
FastAPI предоставляет автоматическую валидацию данных, генерацию документации, встроенную поддержку типизации и асинхронность. Flask более минималистичен и требует дополнительных библиотек для этих возможностей.
Поддерживает ли FastAPI GraphQL?
Да, FastAPI можно интегрировать с GraphQL через библиотеки Strawberry или Graphene.
Можно ли использовать FastAPI с Django ORM?
Да, но это не рекомендуется. Лучше использовать SQLAlchemy, Tortoise ORM или другие асинхронные ORM.
Как обеспечить безопасность FastAPI приложения?
Используйте HTTPS, правильную аутентификацию (JWT, OAuth2), валидацию входных данных, CORS настройки и регулярно обновляйте зависимости.
Поддерживает ли FastAPI миграции базы данных?
FastAPI не включает инструменты для миграций. Используйте Alembic для SQLAlchemy или Aerich для Tortoise ORM.
Можно ли использовать FastAPI для создания полноценных веб-приложений?
FastAPI оптимизирован для создания API. Для полноценных веб-приложений лучше использовать Django или Flask с шаблонами.
FastAPI — это мощный и современный фреймворк, который идеально подходит для создания высокопроизводительных API. Его простота использования, автоматическая документация и отличная производительность делают его отличным выбором для проектов любого масштаба.
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов