В руководстве рассмотрим FastAPI — новый асинхронный Python-фреймворк для создания быстрых API по спецификации OpenAPI.
Вы создадите свое первое приложение на FastAPI, выполняя следующие задачи:
- Установка зависимостей.
- База данных PostgreSQL в контейнере Docker.
- Документация и спецификация OpenAPI.
Изложенная в статье информация пригодится как новичкам в бэкенд-разработке, так и желающим найти практическое применение асинхронному программированию на Python.
Примечание: функции из примеров написаны синхронным способом, но для поддержки асинхронного программирования достаточно превратить функции в сопрограммы при помощи ключевых слов async
и await
, подробнее в официальной документации FastAPI.
1. Установка зависимостей
Для выполнения всех пунктов руководства вам понадобится скачать и установить следующие зависимости:
- Python, интерпретатор версии 3.9 и более новой.
- Docker, контейнеризатор приложений.
- FastAPI, веб-платформа разработки RESTful API.
- Uvicorn, ASGI-сервер.
В качестве менеджера зависимостей для руководства выбран Poetry, но если вы с ним еще не работали, то ничего не мешает установить необходимые пакеты более классическим способом — через pip
, с активированным виртуальным окружением или же без него.
Начнем с папки проекта и пакета-приложения в ней:
~ $ mkdir -p startlabs/lightning
~ $ cd startlabs
~/starlabs $ touch lightning/__init__.py
По умолчанию Poetry создает новую виртуальную среду вне директории проекта, но если вы предпочитаете, чтобы директория виртуального окружения находилась именно внутри проекта, то задайте эту глобальную конфигурацию:
~/starlabs $ poetry config virtualenvs.in-project true
Инициализируйте Poetry-проект:
~/startlabs $ poetry init
- Название пакета:
starlabs
- Версия:
0.1.0 - Описание:
Бег быстрее света с 1940 года! - Автор:
Flash Gordon <[email protected]> - Лицензия:
MIT - Совместимые версии Python:
^3.9 - Хотели бы вы в интерактивном режиме определить основные зависимости (main dependencies)?
👎 Нет. - Хотели бы вы в интерактивном режиме определить зависимости для разработки (development dependencies)?
👎 Нет. - Вы подтверждаете генерацию?
👍 Да.
Теперь установите fastapi
и uvicorn
:
~/starlabs $ poetry add fastapi uvicorn
2. База данных PostgreSQL в контейнере Docker
Создайте файл с расширением .env
для конфигурации базы данных:
DB_USERNAME=flashgordon
DB_PASSWORD=starlabs
DB_DATABASE=lightning
DB_HOST=localhost
DB_PORT=5432
Следом создайте файл с названием docker-compose.yaml
:
~/starlabs $ touch docker-compose.yaml
Определите в данном файле службу базы данных по выбору (в примере указана PostgreSQL):
version: "3.9"
services:
db:
image: bitnami/postgresql
ports:
- "${DB_PORT}:5432"
environment:
- POSTGRES_USER=${DB_USERNAME}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_DATABASE}
Не стесняйтесь выбирать любой другой образ (image
), если вам не нравится bitnami/postgresql
.
Запустите контейнер с базой данных:
~/starlabs $ docker compose up -d
С помощью нижеуказанной команды проверьте, запущен ли контейнер:
~/starlabs $ docker compose ps
Попробуйте получить доступ к базе данных, выполнив команду:
~/starlabs $ docker compose exec db psql lightning --username flashgordon --password
Введите exit
для выхода из интерактивного терминала PostgreSQL.
3. Первое FastAPI-приложение
Давайте сделаем CRUD.
CRUD — акроним, обозначающий четыре базовые функции, используемые при работе с базами данных: создание, чтение, модификация, удаление. (Википедия)
Прежде всего, нужно прочитать настройки из файла .env, чтобы установить соединение с базой данных.
Посмотрите на структуру проекта. Внутри пакета lightning
(он же ваше первое приложение на FastAPI) создайте файл config.py
с кодом:
# ~/starlabs/lightning/config.py
from pydantic import BaseSettings
class DBSettings(BaseSettings):
username: str
password: str
database: str
host: str
port: str
class Config:
env_prefix = "DB_"
env_file = ".env"
Создайте еще один файл под названием dependencies.py
. Данный файл ответственен за разрешение зависимостей приложения:
# ~/starlabs/lightning/dependencies.py
from functools import lru_cache
from . import config
from . import database
# Вызывается по время внедрения зависимости
def get_db() -> Session:
db = database.SessionLocal()
try:
yield db
finally:
db.close()
# Возврат существующего экземпляра DBSettings вместо создания нового
@lru_cache
def get_db_settings() -> config.DBSettings:
return config.DBSettings()
Внедрение зависимости (Dependency injection) — процесс, когда объект отдает заботу о построении требуемых ему зависимостей внешнему, специально предназначенному для этого общему механизму. (Википедия)
В той же папке создайте файл database.py
и определите подключение к базе данных:
# ~/starlabs/lightning/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from .dependencies import get_db_settings
settings = get_db_settings()
SQLALCHEMY_DATABASE_URL = f"postgresql://{settings.username}:{settings.password}@{settings.host}:${settings.port}/{settings.database}"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Model = declarative_base()
Следом, внутри приложения lightning
необходимо создать два пакета:
models
— модели на базе классов представляют данные из таблиц базы данных. Подобная технология называется ORM (объектно-реляционное отображение). База данных представляется в виде конструкций объектно-ориентированного программирования, вроде классов, создается “виртуальная объектная база данных”. В приложении, рассматриваемом данным руководством, управляет всеми этими процессами open-source ORM-библиотека SQLAlchemy.schemas
— схемы выполняют два действия: определяют классы для операций чтения и/или записи моделей, а также обрабатывают автоматические проверки. В FastAPI подобный функционал реализовывается благодаря потрясающей библиотеке pydantic.
Вы уже установили пакет pydantic вместе с пакетом FastAPI. Но кроме стандартного функционала нужно подключить и дополнительные возможности: добавьте SQLAlchemy в качестве зависимости.
Дополнительные возможности pydantic следующие.
- Валидация электронной почты.
- Чтение конфигураций dotenv.
Давайте обновим зависимости проекта:
~/starlabs $ poetry add sqlalchemy psycopg2-binary pydantic[email,dotenv]
Создайте файл speedster.py
внутри пакета models
:
# ~/starlabs/lightning/models/speedster.py
from uuid import uuid4
from sqlalchemy import Column, String, Float
from sqlalchemy.dialects.postgresql import UUID
from ..database import Model
class Speedster(Model):
__tablename__ = "speedsters"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
name = Column(String)
gender = Column(String) # improve with Enum Choice, if you want
email = Column(String, unique=True, index=True)
password = Column(String)
velocity_kms_per_hour = Column(Float)
height_in_cm = Column(Float)
weight_in_kg = Column(Float)
Создайте еще один файл с тем же именем speedster.py
, но в пакете schemas
:
# ~/starlabs/lightning/schemas/speedster.py
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, EmailStr
# Основная схема
class SpeedsterBase(BaseModel):
name: str
gender: Literal["male", "female", "other/non-binary"]
email: EmailStr
velocity_in_kms_per_hour: float
height_in_cm: float
weight_in_kg: float
# Пароль никогда не должен быть возвращен в ответе.
# Для этого используется третья схема, определенная ниже.
# Проверяется только запрос на создание.
class SpeedsterCreate(SpeedsterBase):
password: str
# default schema to return on a response
class Speedster(SpeedsterBase):
id: UUID
class Config:
orm_mode = True # TL;DR; помогает связать модель со схемой
Теперь создайте репозиторий для speedsters
, добавьте в него новый пакет repositories
с файлом speedsters.py
:
# ~/starlabs/lightning/repositories/speedsters.py
from typing import List
from uuid import UUID
from fastapi.params import Depends
from pydantic import EmailStr
from sqlalchemy.orm import Session
from ..models.speedster import Speedster
from ..dependencies import get_db
from ..schemas.speedster import SpeedsterCreate
class SpeedstersRepository:
def __init__(self, db: Session = Depends(get_db)):
self.db = db # произойдет внедрение зависимостей
def find(self, uuid: UUID) -> Speedster:
query = self.db.query(Speedster)
return query.filter(Speedster.id == uuid).first()
def find_by_email(self, email: EmailStr):
query = self.db.query(Speedster)
return query.filter(Speedster.email == email).first()
def all(self, skip: int = 0, max: int = 100) -> List[Speedster]:
query = self.db.query(Speedster)
return query.offset(skip).limit(max).all()
def create(self, speedster: SpeedsterCreate) -> Speedster:
faked_pass_hash = speedster.password + "__you_must_hash_me"
db_speedster = Speedster(
name=speedster.name,
email=speedster.email,
gender=speedster.gender,
velocity_kms_per_hour=speedster.velocity_kms_per_hour,
height_in_cm=speedster.height_in_cm,
weight_in_kg=speedster.weight_in_kg,
password=faked_pass_hash
)
self.db.add(db_speedster)
self.db.commit()
self.db.refresh(db_speedster)
return db_speedster
Пришло время написать конечные точки для speedsters
. Создайте новый пакет под названием routers
с файлом speedsters.py
:
# ~/starlabs/lightning/routers/speedsters.py
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import parse_obj_as
from typing import List
from uuid import UUID
from ..schemas.speedster import Speedster, SpeedsterCreate
from ..repositories.speedsters import SpeedstersRepository
router = APIRouter(prefix="/speedsters", tags=["speedsters"])
@router.get("/", response_model=List[Speedster])
def list_speedsters(skip: int = 0, max: int = 10, speedsters: SpeedstersRepository = Depends()):
db_speedsters = speedsters.all(skip=skip, max=max)
return parse_obj_as(List[Speedster], db_speedsters)
@router.post("/", response_model=Speedster, status_code=status.HTTP_201_CREATED)
def store_speedster(speedster: SpeedsterCreate, speedsters: SpeedstersRepository = Depends()):
db_speedster = speedsters.find_by_email(email=speedster.email)
if db_speedster:
raise HTTPException(
status_code=400,
detail="Email already registered"
)
db_speedster = speedsters.create(speedster)
return Speedster.from_orm(db_speedster)
@router.get("/{speedster_id}", response_model=Speedster)
def get_speedster(speedster_id: UUID, speedsters: SpeedstersRepository = Depends()):
db_speedster = speedsters.find(speedster_id)
if db_speedster is None:
raise HTTPException(
status_code=404,
detail="Speedster not found"
)
return Speedster.from_orm(db_speedster)
Или, если хотите, запишите маршрут router.post
для действия create
немного по-другому:
def create(self, speedster: SpeedsterCreate) -> Speedster:
speedster.password += "__you_must_hash_me"
db_speedster = Speedster(**speedster.dict())
self.db.add(db_speedster)
self.db.commit()
self.db.refresh(db_speedster)
return db_speedster
Отлично, вы почти полностью написали свое первое приложение на FastAPI, отсюда уже видно финишную черту!
Теперь необходимо подключить все старания к экземпляру приложения FastAPI.
В директории по адресу ~/starlabs/lightning/
создайте файл с именем main.py
:
# ~/starlabs/lightning/main.py
from fastapi import FastAPI
from .database import Model, engine
from .routers import speedsters
Model.metadata.create_all(bind=engine)
app = FastAPI()
app.include_router(speedsters.router)
Пришло время запускать приложение, оценивать свои труды!
Примечание: убедитесь, что Docker-контейнер с базой данных запущен.
~/starlabs $ poetry run uvicorn lightning.main:app --reload --debug
Поздравляем, приложение запущено на сервере uvicorn
и готово к обработке CRUD запросов!
4. Документация и спецификация OpenAPI
Среди прочих следует выделить замечательную особенность FastAPI: он поставляется с OpenAPI/Swagger в комплекте.
The OpenAPI Specification (спецификация OpenAPI) — спецификация интерфейса между front-end системами, кодом библиотек низкого уровня и коммерческими решениями. (Википедия)
Итак, перейдите к конечной точке http://localhost:8000/docs
.
Как видите, документация поставляется вместе с FastAPI, вам не нужно ничего настраивать.
Изучите схемы документации и конечные точки, попробуйте CRUD-операции, а следом приступайте к улучшению описываемых документацией рекомендаций.
Например, когда вы открываете конечную точку с HTTP-запросом POST
, создающую новые объекты (действие create
из CRUD), в разделе “Example Value | Schema” показывается пример правильной схемы запроса:
{
"name": "string",
"email": "[email protected]",
"velocity_kms_per_hour": 0,
"height_in_cm": 0,
"weight_in_kg": 0,
"gender": "male",
"password": "string"
}
Это уже хорошая подсказка, ведь она указывает тип каждого ключа в объекте. Но мы можем (и должны) ее улучшить!
Откройте файл ~/startlabs/lightning/schemas/speedsters.py
, чтобы изменить схемы: добавьте в каждую из них внутренний класс Config
.
# ~/starlabs/lightning/schemas/speedster.py
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, EmailStr
# Скорость света!
__VELOCITY_OF_LIGHT__ = 1079252848.8
class SpeedsterBase(BaseModel):
name: str
email: EmailStr
velocity_kms_per_hour: str
height_in_cm: float
weight_in_kg: float
gender: Literal["male", "female", "other/non-binary"]
class Config:
schema_extra = {
"example": {
"name": "Barry Allen",
"gender": "male",
"email": "[email protected]",
"velocity_kms_per_hour": 2 * __VELOCITY_OF_LIGHT__,
"height_in_cm": 182.88,
"weight_in_kg": 88.45,
}
}
class SpeedsterCreate(SpeedsterBase):
password: str
class Config:
schema_extra = {
"example": {
**SpeedsterBase.Config.schema_extra.get("example"),
"password": "secret",
}
}
class Speedster(SpeedsterBase):
id: UUID
class Config:
orm_mode = True
schema_extra = {
"example": {
**SpeedsterBase.Config.schema_extra.get("example"),
"id": "1fd43a2a-c0b9-4bc4-9b38-ec2d1f1b9898",
}
}
Перезагрузите браузер, причем вам не надо вручную останавливать и перезапускать веб-приложение, ведь во время его запуска был указан флаг --reload
.
Если вы все сделали правильно, то увидите лучший пример схемы.
Хотите увидеть действительно хорошую функцию, поставляемую вместе со схемами? Прощайте, надоевшие валидации!
Попробуйте через ключевую точку create
создать объект с неверным e-mail или неправильно указанной половой принадлежностью, а FastAPI без дополнительных настроек вернет вам не только правильный код состояния, но и обработчик исключений по умолчанию для ошибок валидации схем pydantic
.
И это все!
Читайте также:
- Как добавить множественные примеры запросов и ответов в FastAPI
- Завершаем настройку мощного API на Nodejs, GraphQL, MongoDB, Hapi, и Swagger.
- Веб-скрейпинг с нуля на Python: библиотека Beautiful Soup
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Daniel Pinto: Tutorial: FastAPI Playground