Самый простой способ склеить сгенерированный код приложения Flask (Swagger-Codegen) для внутренней реализации

Я имею:

  1. библиотека, которая делает [Материал]
  2. Swagger API-определение, которое примерно #1 с небольшими отличиями для точного сопоставления с REST-сервисом
  3. приложение с флягой сгенерировало #2 с использованием Swagger-Codegen - например, приводит к тому, что функции контроллера python примерно один к одному с #1.

Мое намерение состоит в том, чтобы приложение фляги (весь сгенерированный код) должно было только обрабатывать отображение того фактического API REST и синтаксический анализ параметров, чтобы соответствовать спецификации API, закодированной в swagger. После разбора любого параметра (опять же, сгенерированного кода) он должен обращаться напрямую к моему (не сгенерированному) бэкэнду.

Мой вопрос заключается в том, как наилучшим образом соединить их с ручным редактированием сгенерированного кода python/flask? (Обратная связь о моем дизайне или подробности формального шаблона проектирования, который выполняет это, тоже была бы отличной; я новичок в этом пространстве).

Только что из генератора, я в конечном итоге с функциями Python, как:

def create_task(myTaskDefinition):
    """
    comment as specified in swagger.json
    :param myTaskDefinition: json blah blah blah
    :type myTaskDefinition: dict | bytes
    :rtype: ApiResponse
    """
    if connexion.request.is_json:
        myTaskDefinition = MyTaskTypeFromSwagger.from_dict(connexion.request.get_json())
    return 'do some magic!' # swagger codegen inserts this string :)

На бэкэнде у меня есть настоящая логика:

def create_task_backend(myTaskDefinition):
    # hand-coded, checked into git: do all the things
    return APIResponse(...)

Какой правильный способ получить create_task() звонить create_task_backend()?

Конечно, если я внесу критические изменения в мои спецификации, мне придется вручную обновлять не сгенерированный код; однако есть много причин, по которым я могу захотеть заново сгенерировать свой API (скажем, добавить / уточнить MyTaskTypeFromSwagger класс, или пропустите проверку в git сгенерированном коде вообще), и если мне придется вручную редактировать сгенерированный код API, то все эти изменения сдуваются с каждым повторным созданием.

Конечно, я мог бы написать это с помощью простой грамматики, например. Pyparsing; но хотя я впервые сталкиваюсь с этой проблемой, похоже, что она уже широко решена!

3 ответа

У меня сработал следующий подход:

  • создал три каталога:

    • src - для моего кода,
    • src-gen для кода, сгенерированного чванством,
    • codegen в котором я поставил скрипт, который генерирует сервер вместе с несколькими хитростями.
  • Я скопировал все шаблоны (доступны в сборке Swagger) в codegen/templates и отредактировал controller.mustache ссылаясь на src/server_implтак что он может использовать мой собственный код. Для редактирования используется язык шаблона, поэтому он является общим. Тем не менее, он не идеален (я бы изменил несколько соглашений об именах), но он выполняет свою работу. Итак, сначала добавьте в controller.mustache:

from {packageName}}.server_impl.controllers_impl import {{classname}}_impl

затем добавьте вместо return 'do some magic!' следующие:

return {{classname}}_impl.{{operationId}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
  • Автор сценария:
    • src имеет server_impl каталог.
    • Создает символическую ссылку, чтобы server_impl может быть импортирован как модуль Python
cd ../src-gen/swagger_server/
ln -s ../../src/server_impl/
cd ../../codegen
java -jar swagger-codegen-cli.jar generate  \
-i /path_to_your_swagger definition.yaml \
-l python-flask \
-o ../src-gen \
-t ./templates
cd ../src-gen/
python3 -m swagger_server

Я был соблазн использовать swagger-codegen раньше и столкнулся с той же загадкой. Все хорошо, пока не обновишь спецификацию. Несмотря на то, что вы можете использовать пользовательские шаблоны, это просто потребовало много времени и усилий, когда все, что мне нужно, - это API-интерфейс, ориентированный на дизайн.

Я закончил тем, что вместо этого использовал Connexion, который использует спецификацию Swagger для автоматической обработки маршрутизации, маршалинга, валидации и т. Д. Connexion построен на колбе, поэтому вам не нужно беспокоиться о переключении фреймворков или чего-то еще, вы просто получите преимущество порций Ваше приложение автоматически обрабатывается из Swagger вместо того, чтобы поддерживать автоматически сгенерированный код.

Сейчас я работаю над этим, выполняя эти шаги

  1. запустить коден
  2. Sed-скрипт сгенерированного кода, чтобы исправить тривиальные вещи, такие как пространства имен
  3. отредактируйте вручную файлы, чтобы вместо возврата 'do some magic' (это строка, которую возвращают все сгенерированные конечные точки контроллера), они просто вызывают соответствующую функцию в моем "бэкэнде"
  4. использование git format-patch сделать исправление предыдущих изменений, чтобы при повторном создании кода сборка могла автоматически применить изменения.

Таким образом, я могу добавить новые конечные точки, и мне нужно только вручную закодировать вызовы на мой бэкэнд ~ один раз. Вместо того, чтобы использовать файлы исправлений, я мог бы сделать это напрямую, написав грамматику py-parsing для сгенерированного кода и используя проанализированный сгенерированный код для создания обращений к моему бэкэнду... это заняло бы больше времени, поэтому я сделал все это быстро хак.

Это далеко не оптимально, я не собираюсь отмечать это как принятое, поскольку я надеюсь, что кто-то предложит реальное решение.

Рабочий процесс, к которому я пришел.

Идея состоит в том, чтобы сгенерировать код, а затем извлечь swagger_serverпакет в каталог проекта. Но отдельно храните контроллеры, которые вы кодируете, в отдельном каталоге или (как я) в корне проекта и объединяйте их со сгенерированными после каждого поколения, используяgit merge-files. Затем вам нужно вводить свой свежие контроллеры кода вswagger_server/controllers, т.е. перед запуском сервера.

project
+-- swagger_server
|   +-- controllers
|       +-- controller.py <- this is generated
+-- controller.py <- this is you are typing your code in
+-- controller.py.common <- common ancestor, see below
+-- server.py <- your server code, if any

Итак, рабочий процесс следующий:

  1. Сгенерировать код, скопировать swagger_server в каталог вашего проекта, полностью перезаписать существующие
  2. Резервное копирование controller.py а также controller.py.common из корня проекта
  3. git merge-file controller.py controller.py.common swagger_server/controllers/controller.py
  4. Сделать swagger_server/controllers/controller.py новый общий предок, скопируйте его в controller.py.common, перезаписать существующие

Не стесняйтесь автоматизировать все это с помощью сценария оболочки, т.е.

#!/bin/bash
# Swagger generate server and client stub based on specification, them merge it into the project.
# Use carefully! Commit always before using this script!
# The following structure is assumed:
# .
# +-- my_client
# |   +-- swagger_client
# +-- my_server
# |   +-- swagger_server
# +-- merge.sh <- this script

read -p "Have you commited the project??? " -n 1 -r
if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo 'Commit first!'; exit 1; fi

rm -rf swagger-python-client
rm -rf swagger-python-server

java -jar swagger-codegen-cli.jar generate -i swagger.yaml -l python -o swagger-python-client 
java -jar swagger-codegen-cli.jar generate -i swagger.yaml -l python-flask -o swagger-python-server

# Client - it's easy, just replace swagger_client package
rm -rf my_client/swagger_client
cp -rf swagger-python-client/swagger_client/ my_client

# Server - replace swagger_server package and merge with controllers
rm -rf my_server/.backup
mkdir -p my_server/.backup
cp -rf my_server/swagger_server my_server/.backup


rm -rf my_server/swagger_server
cp -rf swagger-python-server/swagger_server my_server


cd my_server/swagger_server/controllers/
files=$( ls * )
cd ../../..

for f in $files; do

    # skip __init__.py
    if [ -z "$flag" ]; then flag=1; continue; fi
    echo "======== $f"

    # initialization
    cp -n my_server/swagger_server/controllers/$f my_server/$f.common
    cp -n my_server/swagger_server/controllers/$f my_server/$f


    # real merge
    cp -f my_server/$f my_server/.backup/
    cp -f my_server/$f.common my_server/.backup/
    git merge-file my_server/$f my_server/$f.common my_server/swagger_server/controllers/$f
    cp -f my_server/swagger_server/controllers/$f otmini-repo/$f.common

done

rm -rf swagger-python-client
rm -rf swagger-python-server

Используйте связь, как предложено @MrName.

Я впервые начал использовать это вместе с codegen.

openapi-generator generate -i ../myapi.yaml -g python-flask -o .

Это создает каталог с сервером openapi.

  |- openapi_server\
      |--controllers\
           |--mytag._controller.py\
      |--openapi\
           |--my-api.yaml\

Если вы добавляете теги к своим путям в спецификации api, то для каждого тега создается отдельный tagname-controller.py. Для каждого operationId создается функция.

Однако как только это будет установлено, Connexion сможет обрабатывать обновления спецификации API. Если я добавлю новый путь к openapi/my-api.yaml с operationId=new_func, тогда я могу добавить new_func() к существующему контроллеру. Я не теряю существующую логику сервера (но я бы на всякий случай сделал резервную копию). Я еще не пробовал радикально изменить существующие пути.

Другие вопросы по тегам