- Создавать приложение FastAPI и запускать его локально через
fastapi devилиuvicorn - Объявлять параметры пути и запроса с типами и проверять JSON-тело моделью Pydantic
- Возвращать правильные коды состояния и проверять API через
/docsиTestClient
В прошлом уроке мы были клиентом и отправляли запросы к чужому API. Теперь меняемся ролями и напишем свой сервер. FastAPI — современный фреймворк Python: по аннотациям типов (type hints) в параметрах функции он сам проверяет входящие данные, превращает их в JSON и создаёт интерактивную документацию. Бэкенд для мобильного приложения, небольшой микросервис или модель машинного обучения в виде API — для всего этого FastAPI популярный выбор.
Первое приложение и локальный запуск
Сервер не может работать в браузере, поэтому код этого урока здесь не выполняется — попробуй его на своём компьютере (Python 3.10 или новее). Декоратор @app.get('/путь') связывает функцию с GET-запросами по этому адресу; возвращённый функцией словарь автоматически превращается в JSON-ответ.
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Создай виртуальное окружение
В папке проекта выполни
python -m venv .venvи активируй окружение: в Windows —.venv\Scripts\activate, в Linux и macOS —source .venv/bin/activate. - 2Установи FastAPI
pip install "fastapi[standard]"— вместе с FastAPI устанавливаются серверuvicornи командаfastapi. - 3Запусти сервер
fastapi dev main.py(илиuvicorn main:app --reload). Сервер работает по адресуhttp://127.0.0.1:8000и сам перезапускается при изменении файла. - 4Открой документацию
Открой в браузере
http://127.0.0.1:8000/docs— там перечислены все адреса, и их можно сразу опробовать кнопкой «Try it out».
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 для задач; для простоты данные хранятся в словаре в памяти.
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 newtasks.py, часть 1: модели и создание. Клиент не присылает id — его назначает сервер, поэтому модели входа (TaskIn) и выхода (Task) разделены.@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: после каждого изменения кода можно за секунды проверить все адреса.
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 characterField(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.
full в @app.get('/users/{user_id}') с функцией def get_user(user_id: int, full: bool = False)?