Перейти к содержанию
Educora
Продвинутый20 мин27 / 42

Веб-сервис на FastAPI

Создай небольшой REST API на FastAPI: параметры пути и запроса, проверка данных моделями Pydantic, ответы 201 и 404, автоматическая документация (`/docs`), запуск сервера на своём компьютере и тесты через `TestClient`.

Проверь себя
В этом уроке ты узнаешь
  • Создавать приложение FastAPI и запускать его локально через fastapi dev или uvicorn
  • Объявлять параметры пути и запроса с типами и проверять JSON-тело моделью Pydantic
  • Возвращать правильные коды состояния и проверять API через /docs и TestClient

В прошлом уроке мы были клиентом и отправляли запросы к чужому API. Теперь меняемся ролями и напишем свой сервер. FastAPI — современный фреймворк Python: по аннотациям типов (type hints) в параметрах функции он сам проверяет входящие данные, превращает их в JSON и создаёт интерактивную документацию. Бэкенд для мобильного приложения, небольшой микросервис или модель машинного обучения в виде API — для всего этого FastAPI популярный выбор.

Первое приложение и локальный запуск

Сервер не может работать в браузере, поэтому код этого урока здесь не выполняется — попробуй его на своём компьютере (Python 3.10 или новее). Декоратор @app.get('/путь') связывает функцию с GET-запросами по этому адресу; возвращённый функцией словарь автоматически превращается в JSON-ответ.

Python
from fastapi import FastAPI

app = FastAPI(title='Educora Demo API')


@app.get('/')
def root():
    return {'message': 'Hello from FastAPI'}


@app.get('/square/{number}')
def square(number: int):
    return {'number': number, 'square': number ** 2}


@app.get('/greet')
def greet(name: str, lang: str = 'en'):
    greetings = {'en': 'Hello', 'az': 'Salam', 'ru': 'Привет', 'tr': 'Merhaba'}
    word = greetings.get(lang, 'Hello')
    return {'text': f'{word}, {name}!'}
Файл main.py. {number} — параметр пути; name и lang в пути нет, поэтому это параметры запроса, а у lang есть значение по умолчанию, значит, он необязательный.
  1. 1
    Создай виртуальное окружение

    В папке проекта выполни python -m venv .venv и активируй окружение: в Windows — .venv\Scripts\activate, в Linux и macOS — source .venv/bin/activate.

  2. 2
    Установи FastAPI

    pip install "fastapi[standard]" — вместе с FastAPI устанавливаются сервер uvicorn и команда fastapi.

  3. 3
    Запусти сервер

    fastapi dev main.py (или uvicorn main:app --reload). Сервер работает по адресу http://127.0.0.1:8000 и сам перезапускается при изменении файла.

  4. 4
    Открой документацию

    Открой в браузере http://127.0.0.1:8000/docs — там перечислены все адреса, и их можно сразу опробовать кнопкой «Try it out».

Terminal
curl http://127.0.0.1:8000/square/12
curl "http://127.0.0.1:8000/greet?name=Aysel&lang=az"
curl http://127.0.0.1:8000/square/abc
Ожидаемый результат
{"number":12,"square":144}
{"text":"Salam, Aysel!"}
{"detail":[{"type":"int_parsing","loc":["path","number"],"msg":"Input should be a valid integer, unable to parse string as an integer","input":"abc"}]}
Поскольку abc — не целое число, FastAPI даже не вызывает функцию и возвращает код 422 с ответом, объясняющим, где ошибка. В Windows PowerShell пиши curl.exe вместо curl.

REST API с моделями Pydantic

JSON-тело POST-запроса описывает модель Pydantic: класс, наследующий BaseModel, перечисляет поля и их типы, а Field задаёт дополнительные условия (например, заголовок длиной 1–100 символов). Тип возвращаемого значения функции (-> Task) тоже проверяет ответ и отбрасывает лишние поля. Построим небольшой API для задач; для простоты данные хранятся в словаре в памяти.

Python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI(title='Educora Tasks API')


class TaskIn(BaseModel):
    title: str = Field(min_length=1, max_length=100)
    subject: str = 'python'
    done: bool = False


class Task(TaskIn):
    id: int


tasks: dict[int, Task] = {}


@app.post('/tasks', status_code=201)
def create_task(task: TaskIn) -> Task:
    new = Task(id=len(tasks) + 1, **task.model_dump())
    tasks[new.id] = new
    return new
tasks.py, часть 1: модели и создание. Клиент не присылает id — его назначает сервер, поэтому модели входа (TaskIn) и выхода (Task) разделены.
Python
@app.get('/tasks')
def list_tasks(subject: str | None = None, limit: int = 10) -> list[Task]:
    found = [t for t in tasks.values() if subject is None or t.subject == subject]
    return found[:limit]


@app.get('/tasks/{task_id}')
def get_task(task_id: int) -> Task:
    if task_id not in tasks:
        raise HTTPException(status_code=404, detail='Task not found')
    return tasks[task_id]
tasks.py, часть 2: чтение. HTTPException прерывает функцию и отправляет клиенту код 404 с {"detail": "Task not found"}. Запусти сервер командой fastapi dev tasks.py.
ЗапросЧто делаетОтвет
POST /tasksсоздаёт новую задачу201 или 422 (неверное тело)
GET /tasks?subject=python&limit=5возвращает отфильтрованный список200
GET /tasks/{task_id}возвращает одну задачу200 или 404

Тестирование API

TestClient вызывает приложение напрямую, без запуска настоящего сервера, — запросы и ответы при этом полностью настоящие. Это идеально для тестов на pytest: после каждого изменения кода можно за секунды проверить все адреса.

Python
from fastapi.testclient import TestClient
from tasks import app

client = TestClient(app)
r = client.post('/tasks', json={'title': 'Learn FastAPI'})
print(r.status_code, r.json())
client.post('/tasks', json={'title': 'Revise SQL', 'subject': 'sql'})
print(client.get('/tasks', params={'subject': 'python'}).json())
r = client.get('/tasks/99')
print(r.status_code, r.json())
r = client.post('/tasks', json={'title': ''})
print(r.status_code, r.json()['detail'][0]['msg'])
Ожидаемый результат
201 {'title': 'Learn FastAPI', 'subject': 'python', 'done': False, 'id': 1}
[{'title': 'Learn FastAPI', 'subject': 'python', 'done': False, 'id': 1}]
404 {'detail': 'Task not found'}
422 String should have at least 1 character
Пустой заголовок нарушает условие Field(min_length=1) — Pydantic отклоняет запрос ещё до вызова функции. Если subject не передан, берётся значение по умолчанию 'python'.

Главное

  • app = FastAPI() и декораторы @app.get(...), @app.post(...) связывают функции с адресами; возвращённый словарь становится JSON.
  • {имя} в пути — параметр пути, остальные аргументы — параметры запроса; типы проверяются автоматически (при ошибке — 422).
  • BaseModel и Field из Pydantic описывают и проверяют JSON-тело; status_code=201 и HTTPException(404) дают правильные ответы.
  • Локальный запуск: pip install "fastapi[standard]", затем fastapi dev main.py; документация — по адресам /docs и /redoc.
  • TestClient тестирует API без сервера; реальные данные храни в базе данных, а не в памяти.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Что такое full в @app.get('/users/{user_id}') с функцией def get_user(user_id: int, full: bool = False)?