Автоматическая документация для Flask с использованием OpenAPI

от автора

image alt

Техническая документация, как известно, крайне важная часть любого проекта. До недавнего времени мы прекрасно жили с таким генератором документаций как Sphinx. Но наступил момент переходить на технологии с бОльшим набором возможностей, поэтому мы приняли решение переписать нашу документацию на более современный стандарт: OpenAPI Specification. Эта статья является скромным гайдом по такому переезду. Она будет интересна Python-разработчикам, особенно тем, которые используют Flask. После ее прочтения вы узнаете, как создать статическую OpenAPI документацию для Flask приложения и развернуть ее в GitLab Pages.


apispec + marshmallow

В качестве веб-фреймворка у нас используется Flask. Документацию для API, созданного с помощью него, мы и хотим создать. Спецификация по стандарту OpenAPI описывается форматом YAML(или JSON). Чтобы преобразовать докстринги нашего API в необходимый формат, будем использовать такой инструмент, как apispec, и его плагины. Например, MarshmallowPlugin, с помощью которого (и самой библиотеки marshmallow) можно удобно — за счет возможности наследования и переиспользования — описать входные и выходные данные эндпоинтов в виде python классов, а также провалидировать их.

Используя библиотеку marshmallow, создадим класс, описывающий параметры API:

from marshmallow import Schema, fields  class InputSchema(Schema):    number = fields.Int(description="Число", required=True, example=5)    power = fields.Int(description="Степень", required=True, example=2) 

Аналогично сделаем для выходных параметров:

class OutputSchema(Schema):    result = fields.Int(description="Результат", required=True, example=25) 

Для группировки запросов в OpenAPI используются теги. Создадим тег и добавим его в объект APISpec:

def create_tags(spec):    """ Создаем теги.     :param spec: объект APISpec для сохранения тегов    """    tags = [{'name': 'math', 'description': 'Математические функции'}]     for tag in tags:        print(f"Добавляем тег: {tag['name']}")        spec.tag(tag) 

Далее нам нужно интегрировать параметры в докстринг так, чтобы это соответствовало OpenAPI спецификации.

Пример:

from flask import Blueprint, current_app, json, request  blueprint_power = Blueprint(name="power", import_name=__name__)   @blueprint_power.route('/power') def power():    """    ---    get:      summary: Возводит число в степень      parameters:        - in: query          schema: InputSchema      responses:        '200':          description: Результат возведения в степень          content:            application/json:              schema: OutputSchema        '400':          description: Не передан обязательный параметр          content:            application/json:              schema: ErrorSchema      tags:        - math    """    args = request.args     number = args.get('number')    if number is None:        return current_app.response_class(            response=json.dumps(                {'error': 'Не передан параметр number'}            ),            status=400,            mimetype='application/json'        )     power = args.get('power')    if power is None:        return current_app.response_class(            response=json.dumps(                {'error': 'Не передан параметр power'}            ),            status=400,            mimetype='application/json'        )     return current_app.response_class(        response=json.dumps(            {'response': int(number)**int(power)}        ),        status=200,        mimetype='application/json'    ) 

Эта функция — пример реализации метода GET в нашем API.

Блок summary. Краткое описание функции. Для более подробного описания можно добавить блок description.

Блок parameters. Описание параметров запроса. У параметра указывается, откуда он берется:

  • path, для /power/{number}
  • query, для /power?number=5
  • header, для X-MyHeader: Value
  • cookie, для параметров переданных в cookie файле

и schema, в которую передается python класс, описывающий данный параметр.

Блок responses. Описание вариантов ответа команды и их структура.

Блок tags. Описание тегов, которые используются для логической группировки эндпоинтов.

Для POST запроса, например, можно указать еще requestBody, в котором описываются параметры, передаваемые в теле. Подробнее можно почитать в официальной документации.

После того, как мы описали методы API, можем загрузить их описание в объект APISpec:

def load_docstrings(spec, app):    """ Загружаем описание API.     :param spec: объект APISpec, куда загружаем описание функций    :param app: экземпляр Flask приложения, откуда берем описание функций    """    for fn_name in app.view_functions:        if fn_name == 'static':            continue        print(f'Загружаем описание для функции: {fn_name}')        view_fn = app.view_functions[fn_name]        spec.path(view=view_fn) 

Создаем метод get_apispec, который будет возвращать объект APISpec, в нем добавляем общую информацию о проекте и вызываем описанные ранее методы load_docstrings и create_tags:

from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from apispec_webframeworks.flask import FlaskPlugin  def get_apispec(app):    """ Формируем объект APISpec.     :param app: объект Flask приложения    """    spec = APISpec(        title="My App",        version="1.0.0",        openapi_version="3.0.3",        plugins=[FlaskPlugin(), MarshmallowPlugin()],    )     spec.components.schema("Input", schema=InputSchema)    spec.components.schema("Output", schema=OutputSchema)    spec.components.schema("Error", schema=ErrorSchema)     create_tags(spec)     load_docstrings(spec, app)     return spec 
image alt

Swagger UI

Swagger UI позволяет создать интерактивную страницу с документацией.

Создадим эндпоинт, который будет возвращать спецификацию в json формате, и вызываем в нем get_apispec:

@app.route('/swagger') def create_swagger_spec():    return json.dumps(get_apispec(app).to_dict()) 

Теперь, когда мы получили json спецификацию, нам нужно сформировать из неё html документ. Для этого воспользуемся пакетом flask_swagger_ui, с помощью которого можно встроить интерактивную страницу с документацией на базе Swagger UI в наше Flask приложение:

from flask_swagger_ui import get_swaggerui_blueprint  SWAGGER_URL = '/docs' API_URL = '/swagger'  swagger_ui_blueprint = get_swaggerui_blueprint(    SWAGGER_URL,    API_URL,    config={        'app_name': 'My App'    } ) 

Таким образом, мы сделали эндпоинт /docs, при обращении по которому получаем документацию следующего вида:

image alt

image alt

GitLab Pages + ReDoc

Если мы не хотим формировать документацию во время обращения по эндпоинту, то можно собрать статичный документ.

Использование GitLab позволяет сгенерировать такую статическую страницу с документацией в CI/CD процессах.

Таким образом, мы один раз соберем документацию, и при обращении по заданному адресу она будет просто отображаться без какой-либо дополнительной обработки.

Для этого сохраним APISpec в YAML файл:

DOCS_FILENAME = 'docs.yaml'  def write_yaml_file(spec: APISpec):    """ Экспортируем объект APISpec в YAML файл.     :param spec: объект APISpec    """    with open(DOCS_FILENAME, 'w') as file:        file.write(spec.to_yaml())    print(f'Сохранили документацию в {DOCS_FILENAME}’) 

Теперь, когда мы получили YAML файл по спецификации OpenAPI, нужно сформировать HTML документ. Для этого будем использовать ReDoc, так как он позволяет сгенерировать документ в gitlab-ci с красивой и удобной структурой. Публиковать его будем с помощью GitLab Pages.

Добавим следующие строки в файл gitlab-ci.yml:

pages:  stage: docs  image: alpine:latest  script:    - apk add --update nodejs npm    - npm install -g redoc-cli    - redoc-cli bundle -o public/index.html docs.yaml  artifacts:    paths:      - public 

Стоит отметить, что index.html нужно сохранять в папку public, так как она зарезервирована GitLab’ом.

Теперь, если мы запушим изменения в репозиторий, по адресу namespace.gitlab.com/project появится документация:

image alt

Также путь до документации можно посмотреть в Settings/Pages

Пример документации с использованием ReDoc: ivi-ru.github.io/hydra

Заключение

Таким образом, мы научились собирать OpenAPI документацию с использованием ReDoc и хостить ее на GitLab Pages. В эту статью не попало еще несколько возможностей этих инструментов, например, валидация параметров с помощью marshmallow. Но основной ее целью было показать непосредственно процесс создания документации.

Полезные ссылки

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


Комментарии

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

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