REST API на основе Snake (Python, Mamba, Hydra и Fast API)

от автора


Сегодня я хочу попробовать что-то новое и начну исследовать мир 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 рублей.


ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/685302/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *