Как стать автором
Обновить

Библиотека СleanAPI: cоздаем микросервис на Python за 30 секунд

Время на прочтение4 мин
Количество просмотров20K

Использование микросервисной архитектуры для построения корпоративных приложений взамен традиционной монолитной — популярный тренд в веб-разработке.

Я не ставил целью настоящей статьи познакомить читателей с концепцией микросервисов. Желающим получить общее введение в тему могу порекомендовать заглянуть сюда.

Первая проблема, которую вам предстоит решить, столкнувшись на практике с задачей написать микросервис на Python — выбор подходящего фреймворка. Можно, конечно, использовать мегапопулярный Django, но, на мой взгляд, это все равно, что забивать гвоздь при помощи гидромолота. Существуют легковесные и простые в освоении решения, специально предназначенные для построения быстрых серверов API: Flask, Tornado, FastAPI и другие.

Мой выбор — Tornado. Он асинхронный, а значит при правильном подходе к разработке — более производительный. Разрабатывается с 2009 года, а значит давно переболел всеми детскими болезнями и не преподносит сюрпризов с совместимостью при выходе новых релизов. Публикаций в сети по нему накопилось огромное количество, так что, осваивая этот инструмент, вы точно не останетесь один на один со своими проблемами.

Поработав с Tornado в паре коммерческих проектов, я в целом остался доволен результатами. Однако, как бы ни было хорошо, всегда хочется чего-то большего. Например, когда я ввожу новых разработчиков в курс дела, меня слегка напрягает необходимость давать им многословные инструкции где что лежит и куда что надо прописать, чтобы создать точку входа. Нет, можно, конечно, всю бизнес-логику хранить в одном модуле, но как только приложение чуть выходит за рамки «Hello world!», этот подход становится неприемлемым. Хочется так: написал хэндлер, бросил в известную папку и забыл — сервер сам его подхватит и разберется какой адрес ему назначить. Тот же самое и в отношении статического контента. Еще хочется поддержки «из коробки» датаклассов Pydantic (как у FastAPI), быстрого переключения между http и https, логирования, бенчмаркинга и прочих приятных мелочей.

Результатом моих раздумий стала небольшая библиотека CleanAPI, представляющая собой оболочку над Tornado, позволяющую повысить удобство разработки и снизить до предела и без того невысокий порог вхождения для новичков.

CleanAPI — не форк Tornado, а оболочка над ним. Никакого принципиально нового функционала в оригинальный фреймворк я от себя не добавил — исключительно синтаксический сахар.

Устанавливаем библиотеку:

pip install cleanapi

Актуальная версия Tornado подтянется и установится "под капот" автоматически, вам не нужно об этом беспокоиться.

Создаем в корне проекта следующую структуру папок:

Пишем простейший хэндлер simple_handler.py:

from cleanapi.server import BaseHandler

url_tail = '/example.json'

class Handler(BaseHandler):
    async def get(self):
        self.set_status(200)
        self.write({'status': 'working'})

Пишем статическую страничку index.html, например, такую:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Демонстрация CleanAPI</title>
  <link rel="icon" href="/favicon.ico" type="image/x-icon" />
 </head>
<body>
<h1>Демо-страница</h1>
<p>Все в порядке!</p>
</body>
</html>

Если вы перфекционист, то можете еще положить в папку static_html иконку favicon.ico

И наконец, пишем запускаемый скрипт server_example.py:

from cleanapi import server

server.start('http', 8080, '/', './handlers', './static_html')

Результатом любуемся по адресам:

http://localhost:8080

http://localhost:8080/example.json

Обратите внимание: в папке log появился соответствующий лог-файл.

Почти столь же просто стало использование датаклассов Pydantic. Создадим в папке handlers еще один обработчик pydantic_handler.py:

from cleanapi.server import PydanticHandler
from pydantic import BaseModel, validator, NonNegativeInt
from typing import Optional, List


url_tail = '/pydantic.json'


class PydanticRequest(BaseModel):
    foo: NonNegativeInt
    bar: NonNegativeInt

    @validator('foo', 'bar')
    def _validate_foo_bar(cls, val: str):
        if val == 666:
            raise ValueError(f'Values of foo and bar should not be equal to 666')
        return val


class PydanticResponse(BaseModel):
    summ: Optional[NonNegativeInt]
    errors: Optional[List[dict]]


class Handler(PydanticHandler):
    request_dataclass = PydanticRequest
    result_dataclass = PydanticResponse

    def process(self, request: request_dataclass) -> result_dataclass:
        result = PydanticResponse(summ=request.foo + request.bar, errors=[])

        if result.summ > 1000:
            raise ValueError('The sum of foo and bar is more than 1000')

        return result

    def if_exception(self, errors: list) -> None:
        self.set_status(400)
        self.write({'errors': errors})
        return

Результат наблюдаем по адресу: http://localhost:8080/pydantic.json

Для этого надо методом POST передать запрос в формате json. Браузером это сделать не получится, надо использовать утилиту типа Postman или самописный скрипт вроде:

import requests
import json
import urllib3
from funnydeco import benchmark

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
host = 'http://localhost:8080'
url_pydantic = f'{host}/pydantic.json'
headers = {'Content-type': 'application/json'}

params_pydantic = {
                     "foo": 8,
                     "bar": 4
                   }
@benchmark
def requester(url: str, params: dict, print_benchmark=False, benchmark_name='') -> None:
    response = requests.post(url, verify=False, data=json.dumps(params), headers=headers)
    print('Server response:')
    print(f'Status code - {response.status_code}')
    print(json.dumps(response.json(), sort_keys=False, indent=4, ensure_ascii=False))

if __name__ == '__main__':
    print_bench = True
    bench_name = 'Request execution'
    requester(url_pydantic, params_pydantic, print_benchmark=print_bench, benchmark_name=bench_name)

Поэкспериментируйте с разными значениями foo и bar, убедитесь, что логика, заложенная в обработчике, отрабатывает как надо.

Для адаптации приложения к вашей бизнес-логике вы можете создавать свои классы хэндлеров, унаследовав их от BaseHandler или PydanticHandler.

В своем текущем проекте мне удалось за счет использования такой схемы сократить изначальный объем кода API-сервера (а он был не маленький, учитывая, что я сейчас использую более 20 точек входа) более чем в три раза.

В общем, если вам понравилось, пользуйтесь на здоровье. Придумаете свои интересные фишки — делайте пул-реквесты на https://github.com/vlakir/cleanapi.

Всем добра!

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 10: ↑9 и ↓1+8
Комментарии11

Публикации

Истории

Работа

Data Scientist
62 вакансии
Python разработчик
135 вакансий

Ближайшие события