Перейти к содержимому

FastAPI: современный фреймворк для создания высокопроизводительных API

FastAPI: современный фреймворк для создания высокопроизводительных API

FastAPI — это современный, быстрый (высокопроизводительный) веб-фреймворк для создания API с использованием Python 3.7+ на основе стандартов OpenAPI и JSON Schema. Он сочетает в себе скорость разработки, типизацию и автоматическую документацию, что делает его идеальным инструментом для создания надежных API.

В этой статье вы научитесь:

  • Создавать API с помощью FastAPI и Pydantic
  • Обрабатывать различные типы параметров (query, path, body)
  • Работать с автоматической документацией Swagger UI
  • Использовать типизированные модели данных для валидации

Почему FastAPI?

FastAPI быстро набирает популярность в Python-сообществе благодаря своим преимуществам:

  • Высокая производительность — один из самых быстрых Python-фреймворков, сравнимый с Node.js и Go
  • Автоматическая документация — генерирует интерактивную документацию Swagger UI и ReDoc
  • Типизация Python 3.7+ — использует аннотации типов для валидации данных
  • Простота и интуитивность — минималистичный синтаксис с максимальной функциональностью
  • Асинхронная поддержка — встроенная поддержка async/await для высоконагруженных приложений

FastAPI идеально подходит для:

  • Создания RESTful API и микросервисов
  • Быстрой разработки прототипов и MVP
  • Интеграции с машинным обучением и Data Science
  • Создания внутренних инструментов и админ-панелей

Установка и первый запуск

Для начала работы с FastAPI необходимо установить сам фреймворк и ASGI-сервер для запуска приложения.

Установка зависимостей

pip install fastapi uvicorn
 

Результат выполнения в терминале:

Collecting fastapi
  Downloading fastapi-0.103.2-py3-none-any.whl (86 kB)
Collecting uvicorn
  Downloading uvicorn-0.23.2-py3-none-any.whl (58 kB)
Collecting pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,<3.0.0,>=1.7.4 (from fastapi)
  Downloading pydantic-2.4.2-cp311-cp311-win_amd64.whl (2.2 MB)
Collecting starlette<0.28.0,>=0.27.0 (from fastapi)
  Downloading starlette-0.27.0-py3-none-any.whl (64 kB)
Installing collected packages: pydantic, starlette, fastapi, uvicorn
Successfully installed fastapi-0.103.2 pydantic-2.4.2 starlette-0.27.0 uvicorn-0.23.2
  

Простейшее FastAPI-приложение

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Привет, мир!"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
 

Результат выполнения в терминале:

INFO:     Started server process [12345]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
  

Теперь приложение доступно по адресу http://localhost:8000, а документация — по http://localhost:8000/docs.


Работа с параметрами в FastAPI

FastAPI предоставляет несколько способов получения данных из запросов: через параметры пути (path), через строку запроса (query) и из тела запроса (body).

Параметры пути (Path Parameters)

Параметры пути указываются непосредственно в пути URL и используются для идентификации конкретных ресурсов.

from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
async def read_user(user_id: int):
    return {"user_id": user_id, "name": f"User {user_id}"}

@app.get("/products/{product_id}/reviews")
async def get_product_reviews(product_id: str):
    return {"product_id": product_id, "reviews": ["Отличный товар", "Неудобная упаковка"]}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
 

Пример запроса и ответа:

GET /users/42
Response: {"user_id": 42, "name": "User 42"}

GET /products/book-123/reviews
Response: {"product_id": "book-123", "reviews": ["Отличный товар", "Неудобная упаковка"]}
  

Параметры строки запроса (Query Parameters)

Параметры строки запроса передаются после знака вопроса в URL и используются для фильтрации, пагинации и других операций.

from fastapi import FastAPI
from typing import Optional

app = FastAPI()

@app.get("/search")
async def search_items(
    query: str,
    page: int = 1,
    size: int = 10,
    sort_by: Optional[str] = None
):
    return {
        "query": query,
        "page": page,
        "size": size,
        "sort_by": sort_by,
        "results": [f"Результат {i}" for i in range(1, size + 1)]
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
 

Пример запроса и ответа:

GET /search?query=python&page=2&size=5&sort_by=date
Response: {
    "query": "python",
    "page": 2,
    "size": 5,
    "sort_by": "date",
    "results": ["Результат 1", "Результат 2", "Результат 3", "Результат 4", "Результат 5"]
}
  

Параметры тела запроса (Request Body)

Для передачи сложных данных используется тело запроса в формате JSON. FastAPI автоматически парсит и валидирует эти данные.

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    description: Optional[str] = None
    in_stock: bool = True

@app.post("/items/")
async def create_item(item: Item):
    return {
        "message": "Товар успешно создан",
        "item": item.dict(),
        "total_cost": item.price * 1.2  # Пример бизнес-логики
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
 

Пример запроса и ответа:

POST /items/
Body: {
    "name": "Ноутбук",
    "price": 999.99,
    "description": "Мощный ноутбук для разработки",
    "in_stock": true
}

Response: {
    "message": "Товар успешно создан",
    "item": {
        "name": "Ноутбук",
        "price": 999.99,
        "description": "Мощный ноутбук для разработки",
        "in_stock": true
    },
    "total_cost": 1199.988
}
  

Pydantic модели: User, Author, Post

Pydantic — это библиотека для валидации данных с использованием аннотаций типов Python. В FastAPI она используется для определения моделей данных, автоматической валидации и генерации документации.

Определение моделей

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr, Field
from typing import List, Optional
from datetime import datetime

app = FastAPI()

class User(BaseModel):
    id: int
    username: str = Field(..., min_length=3, max_length=50)
    email: EmailStr
    is_active: bool = True

class Author(BaseModel):
    id: int
    name: str
    bio: Optional[str] = None
    user_id: int

class Post(BaseModel):
    id: Optional[int] = None
    title: str = Field(..., min_length=5, max_length=200)
    content: str = Field(..., min_length=10)
    author: Author
    created_at: datetime = Field(default_factory=datetime.now)
    tags: List[str] = []

    class Config:
        schema_extra = {
            "example": {
                "title": "Мой первый пост",
                "content": "Это содержимое моего первого поста в блоге",
                "author": {
                    "id": 1,
                    "name": "Иван Иванов",
                    "bio": "Профессиональный разработчик",
                    "user_id": 1
                },
                "tags": ["python", "fastapi", "web"]
            }
        }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
 

Особенности моделей:

  • Field(..., min_length=3) — задаёт ограничения на поля
  • EmailStr — специальный тип для валидации email
  • default_factory=datetime.now — динамическое значение по умолчанию
  • schema_extra — примеры для документации Swagger
  • Config — настройки модели

Создание API с моделями User, Author, Post

Теперь создадим полноценное API для управления пользователями, авторами и постами с использованием определённых ранее моделей.

Базовое CRUD API

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr, Field
from typing import List, Optional, Dict
from datetime import datetime
import uvicorn

app = FastAPI(title="Blog API", version="1.0.0")

# Модели данных
class User(BaseModel):
    id: int
    username: str = Field(..., min_length=3, max_length=50)
    email: EmailStr
    is_active: bool = True

class Author(BaseModel):
    id: int
    name: str
    bio: Optional[str] = None
    user_id: int

class Post(BaseModel):
    id: Optional[int] = None
    title: str = Field(..., min_length=5, max_length=200)
    content: str = Field(..., min_length=10)
    author_id: int
    created_at: datetime = Field(default_factory=datetime.now)
    tags: List[str] = []

# Имитация базы данных
users_db: Dict[int, User] = {}
authors_db: Dict[int, Author] = {}
posts_db: Dict[int, Post] = {}
user_counter = 1
author_counter = 1
post_counter = 1

# Эндпоинты для пользователей
@app.post("/users/", response_model=User)
async def create_user(user: User):
    global user_counter
    user.id = user_counter
    users_db[user_counter] = user
    user_counter += 1
    return user

@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: int):
    if user_id not in users_db:
        raise HTTPException(status_code=404, detail="Пользователь не найден")
    return users_db[user_id]

# Эндпоинты для авторов
@app.post("/authors/", response_model=Author)
async def create_author(author: Author):
    global author_counter
    author.id = author_counter
    authors_db[author_counter] = author
    author_counter += 1
    return author

@app.get("/authors/{author_id}", response_model=Author)
async def get_author(author_id: int):
    if author_id not in authors_db:
        raise HTTPException(status_code=404, detail="Автор не найден")
    return authors_db[author_id]

# Эндпоинты для постов
@app.post("/posts/", response_model=Post)
async def create_post(post: Post):
    global post_counter
    post.id = post_counter
    posts_db[post_counter] = post
    post_counter += 1
    return post

@app.get("/posts/{post_id}", response_model=Post)
async def get_post(post_id: int):
    if post_id not in posts_db:
        raise HTTPException(status_code=404, detail="Пост не найден")
    return posts_db[post_id]

@app.get("/posts/", response_model=List[Post])
async def list_posts(skip: int = 0, limit: int = 10):
    return list(posts_db.values())[skip:skip + limit]

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
 

Результат выполнения в терминале при запуске:

INFO:     Started server process [12345]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
  

Swagger UI: автоматическая документация API

Одной из самых мощных особенностей FastAPI является автоматическая генерация интерактивной документации с помощью Swagger UI.

Доступ к документации

После запуска приложения документация доступна по адресу:

  • http://localhost:8000/docs — интерактивная документация Swagger UI
  • http://localhost:8000/redoc — альтернативная документация ReDoc

Пример использования Swagger UI

В Swagger UI вы можете:

  • Просматривать все эндпоинты API
  • Видеть параметры запросов и примеры тела запроса
  • Выполнять тестовые запросы прямо из браузера
  • Просматривать схемы данных (Pydantic модели)
  • Экспортировать спецификацию OpenAPI в формате JSON/YAML

Пример интерфейса Swagger UI для нашего API:

{
  "openapi": "3.1.0",
  "info": {
    "title": "Blog API",
    "version": "1.0.0"
  },
  "paths": {
    "/users/": {
      "post": {
        "summary": "Create User",
        "operationId": "create_user_users__post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/User"}
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/User"}
              }
            }
          }
        }
      }
    },
    "/posts/": {
      "get": {
        "summary": "List Posts",
        "operationId": "list_posts_posts__get",
        "parameters": [
          {
            "name": "skip",
            "in": "query",
            "schema": {"type": "integer", "default": 0}
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {"type": "integer", "default": 10}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {"$ref": "#/components/schemas/Post"}
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create Post",
        "operationId": "create_post_posts__post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/Post"}
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/Post"}
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "User": {
        "properties": {
          "id": {"type": "integer"},
          "username": {"type": "string", "minLength": 3, "maxLength": 50},
          "email": {"type": "string", "format": "email"},
          "is_active": {"type": "boolean", "default": true}
        },
        "required": ["id", "username", "email"],
        "type": "object"
      },
      "Post": {
        "properties": {
          "id": {"type": "integer"},
          "title": {"type": "string", "minLength": 5, "maxLength": 200},
          "content": {"type": "string", "minLength": 10},
          "author_id": {"type": "integer"},
          "created_at": {"type": "string", "format": "date-time"},
          "tags": {
            "items": {"type": "string"},
            "type": "array",
            "default": []
          }
        },
        "required": ["title", "content", "author_id"],
        "type": "object"
      }
    }
  }
}
  

Тестирование API через Swagger UI

В Swagger UI можно протестировать любой эндпоинт:

  1. Выберите эндпоинт (например, POST /users/)
  2. Нажмите кнопку "Try it out"
  3. Введите пример данных в поле "Request body":
{
  "username": "ivan_ivanov",
  "email": "Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.",
  "is_active": true
}
 
  1. Нажмите "Execute"
  2. Посмотрите результат в разделе "Response body"

Практический пример: полный блог с авторизацией

Давайте создадим более сложный пример с аутентификацией и вложенными моделями.

from fastapi import FastAPI, Depends, HTTPException, status
from pydantic import BaseModel, EmailStr, Field
from typing import List, Optional
from datetime import datetime, timedelta
from jose import jwt
from passlib.context import CryptContext

app = FastAPI(title="Advanced Blog API", version="2.0.0")

# Настройки безопасности
SECRET_KEY = "your-secret-key-here"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# Модели данных
class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=50)
    email: EmailStr
    password: str = Field(..., min_length=8)

class UserInDB(BaseModel):
    id: int
    username: str
    email: EmailStr
    hashed_password: str
    is_active: bool = True

class Token(BaseModel):
    access_token: str
    token_type: str

class AuthorCreate(BaseModel):
    name: str
    bio: Optional[str] = None

class Author(BaseModel):
    id: int
    name: str
    bio: Optional[str] = None
    user_id: int

class PostCreate(BaseModel):
    title: str = Field(..., min_length=5, max_length=200)
    content: str = Field(..., min_length=10)
    tags: List[str] = []

class Post(BaseModel):
    id: int
    title: str
    content: str
    author: Author
    created_at: datetime
    tags: List[str]

# Функции для работы с паролями
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})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

# Имитация базы данных
users_db = {}
authors_db = {}
posts_db = {}
user_counter = 1
author_counter = 1
post_counter = 1

# Эндпоинты аутентификации
@app.post("/token", response_model=Token)
async def login_for_access_token(user: UserCreate):
    # В реальном приложении здесь должна быть проверка в БД
    user_in_db = users_db.get(user.username)
    if not user_in_db or not verify_password(user.password, user_in_db.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Неверное имя пользователя или пароль",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

# Эндпоинты для пользователей
@app.post("/users/", response_model=UserInDB)
async def create_user(user: UserCreate):
    global user_counter
    if user.username in users_db:
        raise HTTPException(status_code=400, detail="Пользователь уже существует")
    
    hashed_password = get_password_hash(user.password)
    new_user = UserInDB(
        id=user_counter,
        username=user.username,
        email=user.email,
        hashed_password=hashed_password
    )
    users_db[user.username] = new_user
    user_counter += 1
    return new_user

# Вспомогательная функция для получения текущего пользователя
def get_current_user():
    # В реальном приложении здесь должна быть проверка токена
    # Для упрощения возвращаем первого пользователя
    return list(users_db.values())[0] if users_db else None

# Эндпоинты для авторов
@app.post("/authors/", response_model=Author)
async def create_author(author: AuthorCreate):
    # В реальном приложении здесь должна быть проверка прав пользователя
    global author_counter
    current_user = get_current_user()
    if not current_user:
        raise HTTPException(status_code=401, detail="Необходима аутентификация")
    
    new_author = Author(
        id=author_counter,
        name=author.name,
        bio=author.bio,
        user_id=current_user.id
    )
    authors_db[author_counter] = new_author
    author_counter += 1
    return new_author

# Эндпоинты для постов
@app.post("/posts/", response_model=Post)
async def create_post(post: PostCreate):
    global post_counter
    current_user = get_current_user()
    if not current_user:
        raise HTTPException(status_code=401, detail="Необходима аутентификация")
    
    # Находим автора текущего пользователя
    author = None
    for auth in authors_db.values():
        if auth.user_id == current_user.id:
            author = auth
            break
    
    if not author:
        raise HTTPException(status_code=400, detail="Автор не найден для пользователя")
    
    new_post = Post(
        id=post_counter,
        title=post.title,
        content=post.content,
        author=author,
        created_at=datetime.now(),
        tags=post.tags
    )
    posts_db[post_counter] = new_post
    post_counter += 1
    return new_post

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
 

Чек-лист для проверки усвоения материала

Проверяемый навык / знаниеСтатус
1 Я понимаю, что такое FastAPI и какие преимущества он предоставляет.
Я могу установить FastAPI и запустить базовое приложение.
Я знаю, как получить доступ к автоматической документации Swagger UI.
2 Я умею работать с параметрами пути (path parameters).
Я умею работать с параметрами строки запроса (query parameters).
Я умею работать с параметрами тела запроса (request body) с помощью Pydantic.
3 Я понимаю назначение Pydantic и аннотаций типов в FastAPI.
Я могу создавать и использовать Pydantic модели для валидации данных.
Я умею задавать ограничения на поля моделей (min_length, max_length, etc).
Я понимаю, как использовать вложенные модели и списки в Pydantic.
4 Я могу просматривать и тестировать API через Swagger UI.
Я понимаю структуру OpenAPI спецификации, генерируемой FastAPI.
Я умею настраивать метаданные API (title, version, description).
5 Я могу создать CRUD API для работы с моделями данных.
Я понимаю, как реализовать базовую аутентификацию в FastAPI.
Я умею обрабатывать ошибки и возвращать соответствующие HTTP статусы.
6 Я знаю о существовании асинхронных эндпоинтов (async/await) в FastAPI.
Я понимаю, как интегрировать FastAPI с базами данных и внешними сервисами.

Практическое задание для закрепления

  1. Создайте базовое FastAPI приложение с эндпоинтом /health, который возвращает статус "ok".
  2. Реализуйте CRUD API для управления товарами (Product) с полями:
    • id (int)
    • name (str, min_length=2)
    • price (float, > 0)
    • description (optional str)
    • in_stock (bool, default=True)
  3. Добавьте эндпоинты:
    • GET /products/ — список товаров с пагинацией
    • GET /products/{product_id}  — получение товара по ID
    • POST /products/ — создание нового товара
    • PUT /products/{product_id}  — обновление товара
    • DELETE /products/{product_id}  — удаление товара
  4. Настройте документацию:
    • Добавьте заголовок и описание для API
    • Настройте примеры для эндпоинтов в Swagger UI
  5. Реализуйте поиск:
    • Добавьте эндпоинт GET /products/search с параметрами query и max_price
    • Реализуйте логику поиска по названию и фильтрации по цене

После выполнения заданий вы будете уверенно создавать API с помощью FastAPI, понимать работу с различными типами параметров и эффективно использовать автоматическую документацию. Эти навыки являются основой для разработки современных веб-приложений и микросервисов.

Совет: Начинайте с простого — создайте базовое API и постепенно добавляйте сложные функции. Используйте Swagger UI для тестирования и отладки, это значительно ускорит процесс разработки.

Удачной разработки! 

Конспект:
Пятница, 12 декабря 2025
FastAPI: современный фреймворк для создания высокопроизводительных API