Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Сегодня я хочу попробовать что-то новое и начну исследовать мир Python. В этой статье представлен пошаговый туториал по реализации простого REST API при помощи Python, Fast API, Hydra и Mamba. Более того, я вкратце опишу, как упаковать всех этих змей в один образ Docker и заставить их работать вместе. Весь код выложен на моём GitHub.
Давайте начнём с кратного объяснения того, почему я решил выбрать эту тему.
▍ Почему это интересно?
Для начала, я хотел поделиться своими знаниями, поскольку недавно мне довелось реализовать REST API на основе Python. Выбор фреймворка оказался простым — FAST API. Также нам нужен был инструмент управления зависимостями, поэтому мы выбрали Mamba. Для управления конфигурациями и загрузкой мы решили выбрать Hydra. Нам показалось, что все эти инструменты хорошо работают и предоставляют все необходимые функции, поэтому такое сочетание казалось вполне приемлемым. К сожалению, когда мы перешли к интеграции инструментов и попытались заставить их совместно работать, всё оказалось не так просто. Более того, выяснилось, что информационных ресурсов и примеров по этой теме довольно мало. Именно поэтому я решил написать данную статью.
▍ Что такое Mamba?
Мамба — это смертельно опасный род змей. Ну, а если серьёзно, Mamba — это инструмент для управления зависимостями в проекте и для создания виртуальных окружений Python. Он создан на основе Anaconda, но должен был стать намного быстрее, и судя по моему краткому опыту работы с Mamba могу сказать, что он действительно быстр. Благодаря тому, что он разработан на основе Conda, у нас есть доступ ко всем готовым пакетам из репозиториев Conda. Более того, Mamba API в целом очень похож на Conda, что упрощает пользователям Conda переход на Mamba.
▍ Что такое Fast API?
Это инструмент, который, вероятно известен практически всем в сообществе Python: асинхронный, быстрый и нетребовательный к ресурсам инструмент для создания REST API. Наверно, сегодня это инструмент, который можно рекомендовать всем, кто хочет начать изучение Python и REST. Он содержит все функции для создания работающего API, вместе с WebSockets и поддержкой потоковой передачи. Более того, FAST API использует аннотации типов Python, поэтому автодополнение кода в IDE работает вполне неплохо (по крайней мере, в PyCharm). На мой взгляд, ещё одной довольно полезной функцией является встроенная поддержка swagger. На самом деле, меня удивило её наличие.
▍ Что такое Hydra?
Гидра — это чудовище со множеством голов из древнегреческой мифологии. Hydra — это опенсорсный инструмент для управления и выполнения конфигураций приложений на основе Python. Он основан на библиотеке Omega-Conf. Цитата с главной страницы инструмента: «Ключевой особенностью является возможность динамического создания иерархической конфигурации композированием и переопределением при помощи файлов конфигурации и командной строки». Для меня описанная в цитате иерархическая конфигурация оказалась очень полезной. В моём случае она работала вполне неплохо и обеспечивала более чёткое разделение файлов конфигурации.
▍ Реализация
1. Давайте приступим к проекту, создав
environment.yaml
с конфигурацией нашего окружения.name: greeter-service
channels:
- conda-forge
dependencies:
- python=3.8.13
- pip
- fastapi
- uvicorn[standard]
- hydra-core
- pytest
Файл содержит имя нашего нового виртуального окружения (greeter-service), а также источник, из которого нужно скачивать зависимости (conda-forge), и полный список зависимостей, требуемых для правильной работы приложения. Благодаря Mamba, я могу настроить всё окружение за считанные минуты, при помощи одной простой команды:
mamba env create -n greeter-service --file environment.yaml
При установке Mamba я рекомендую использовать туториал, написанный самими авторами Mamba.
2. На этом этапе я определю файл
config.yaml
со всей конфигурацией, необходимой приложению.app:
port: ${oc.env:GREETER_API_PORT,8070}
version: ${oc.env:GREETER_API_VERSION,v1}
greet_message: "Hello, "
Здесь нет ничего особенного, довольно простой файл
.yaml
с небольшой магией, подключенной при помощи считывания переменных окружения. Это вся конфигурация, которую я буду использовать в своём туториале. Структура довольно стандартна:- порт, на котором будет работать наш API
- версия API, которая будет использоваться в конечных точках
Единственный нестандартный элемент — это параметр
greet_message
, содержащий основу сообщения, которое будет возвращаться пользователю.3. Я добавляю файл
config.py
, отвечающий за считывание конфигурации Hydra.import os
import hydra
from hydra import compose
hydra.initialize_config_dir(config_dir=os.getenv('GREETER_CONFIG_DIR'))
api_config = compose(config_name='config')
Сначала я инициализирую контекст Hydra на основании папки
config
по пути ./
. Hydra будет использовать переменные окружения или брать корневую папку проекта. Затем я использую метод композирования из Hydra для считывания конфигурации, определённой на предыдущем этапе.4. Далее я реализую первую конечную точку API. Я задам её в файле
health_check.py
, поскольку она будет отвечать за обработку запросов проверки состояния.from fastapi import APIRouter
health_router = APIRouter(prefix='/health', tags=['health_checks'])
@health_router.get('', status_code=200)
def is_ok():
return 'Ok'
Код прост и понятен. Это просто роутер FAST API с одним методом, возвращающим при вызове
Ok
и код HTTP 200.5. На этом этапе я создаю файл
greeter.py
, отвечающий за обработку входящих запросов.from fastapi import APIRouter
from api.config import api_config
greeting_router = APIRouter(tags=['greet'])
@greeting_router.get('/greet/{name}', status_code=200)
def say_hello(name: str):
return api_config.app.greet_message + name
Ещё одна простая базовая конечная точка FAST API, получающая на входе имя пользователя. Затем она соединяет переданное имя со считанным из конфигурации форматом сообщений и возвращает пользователю готовое сообщение с приветствием.
6. Теперь я реализую файл
main.py
, который связывается с роутерами из предыдущих этапов.import uvicorn
from fastapi import FastAPI, APIRouter
from api.config import api_config
from api.greeter_api import greeting_router
from api.health_check import health_router
main_api = FastAPI()
main_router = APIRouter(prefix=f'/{api_config.app.version}')
main_router.include_router(health_router)
main_router.include_router(greeting_router)
main_api.include_router(main_router)
def start():
uvicorn.run(main_api, host='0.0.0.0', port=api_config.app.port)
Это просто обычный код FAST API. Примечательно здесь то, что я добавляю версию как базовый префикс ко всем конечным точкам. Самый важный метод здесь — это метод
start
, в котором я вручную запускаю сервер uvicorn (это не опечатка, а настоящее имя сервера) на считанном из конфигурации порту.Мой простой сервис готов к тестированию, но не бойтесь, это не конец нашего туториала. Теперь я расскажу о том, как сделать так, чтобы он работал в качестве образа Docker.
7. Эту часть я начну с определения файла
setup.py
.from setuptools import setup
setup(
name='greeter-service',
version='1.0',
packages=['api'],
entry_points={
'console_scripts': [
'greeter-service = api.main:start',
]
}
)
Самый важный параметр в этом скрипте — это
entry_points
; по сути, он определяет, какой метод Python отвечает за приложение. В данном случае это метод start
из main.py
. Также он определяет имя сервиса Python, который можно использовать для выполнения приложения из командной строки.8. Настало время подготовить
Dockerfile
.FROM condaforge/mambaforge
WORKDIR /greeter-service
COPY environment.yaml environment.yaml
RUN mamba env create -n greeter-service --file environment.yaml
COPY api api
COPY config config
COPY setup.py setup.py
ENV PATH /opt/conda/envs/greeter-service/bin:$PATH
RUN /bin/bash -c "source activate greeter-service" && python setup.py install
Что здесь происходит?
- Для начала, я использую официальный образ Mamba, потому что не хочу тратить время на установку Mamba в Docker с нуля.
- Затем я задаю greeter-service в качестве рабочей папки и добавляю
CONFID_DIR
в качестве новой переменной окружения. - Эту переменную будет использовать Hydra как путь к файлам конфигурации приложения. На следующем этапе я скопировал файл окружения и использовал его для создания виртуального окружения Mamba в Docker.
- Несколько следующих строк — это обычные копии папок с кодом приложения и конфигурацией.
- Последние две строки — это своего рода хак для Conda, не особо хорошо работающего с Docker и в какой-то степени с самим шеллом.
Без этого хака мы бы видели такие сообщения об исключениях:
Your shell has not been properly configured to use 'conda activate and you will not be able to execute conda activate greeter-service
. К сожалению, похоже, ни одно другое исправление здесь не работает, по крайней мере, в моём случае. Некоторые ссылки по теме можно найти здесь, здесь и здесь.9. Вишенкой на торте станет файл композирования Docker, сильно упрощающий настройку образа Docker.
version: "3.9"
services:
greeter-api:
build: .
command: greeter-service
ports:
- "8070:8070"
environment:
- GREETER_CONFIG_DIR=/greeter-service/config
- GREETER_API_PORT=8070
Пока это просто один сервис Docker с двумя использованными переменными окружения — путём к папке с конфигурацией и портом. Compose соберёт образ Docker на основании
Dockerfile
в локальной папке. Далее он использует greeter-service
как команду запуска контейнера. Также он откроет и привяжет локальный порт 8070 с портом 8070 контейнера.Вуаля, реализация готова. Настала пора выполнить тестирование.
▍ Тестирование
Как и в других своих статьях, для выполнения тестов работающего API я использую Postman. Давайте начнём тестирование с настройки образа Docker. Для настройки всего окружения тестирования достаточно простой команды
docker compose build && docker compose up
, по крайней мере, если всё работает, как задумано.Docker запущен, так что я могу протестировать API. Один-два простых запроса, и я удостоверюсь, что всё работает так, как должно. Давайте начнём с конечной точки
greet
.А теперь конечная точка
health
.Немного логов из контейнера Docker, чтобы доказать, что мы не просто нарисовали эти скриншоты:
Мы завершили кодинг и тестирование, настала пора вкратце подвести итоги.
▍ Заключение
Интеграция была реализована, протестирована и описана при помощи не самых известных инструментов, которые я решил использовать. Пример достаточно прост, но содержит всё, что может понадобиться для запуска в любом месте. Более того, его можно легко расширить и использовать как фундамент для более сложных проектов. Надеюсь, статья была для вас полезной.
Конкурс статей от RUVDS.COM. Три денежные номинации. Главный приз — 100 000 рублей.