От документации к готовому API: Генерация кода из Swagger для FastAPI

от автора

В этой статье я расскажу о Swagger и о том, как сгенерировать API и Pydantic модели из Swagger-документации для FastAPI, используя инструмент OpenAPI Generator. В конце статьи вы найдете ссылки на исходный код.

Итак, давайте разбираться!

Для чего это нужно?

Когда вы работаете над API, написание ручного кода для каждого маршрута и модели данных может занять много времени, особенно если система сложная. Описав API в формате OpenAPI (ранее известный как Swagger), вы сможете автоматически генерировать готовые маршруты и Pydantic-модели, которые легко интегрируются в FastAPI. Это освобождает вас от рутины написания однотипных классов и функций, позволяя сосредоточиться на логике приложения.

Что такое Swagger

Swagger — это набор инструментов, облегчающих разработку, тестирование и документирование API. Его основной компонент — это спецификаций в формате OpenAPI, который используется для описания структуры API, доступных маршрутов, типов данных и взаимодействий. Благодаря этому формату Swagger позволяет генерировать документацию, тестировать API, а также создавать клиентские и серверные SDK для различных языков программирования. В этой статье мы сосредоточимся на генерации исходного кода для FastAPI с помощью OpenAPI Generator.

Генерация исходного кода

Для начала необходимо написать документацию нашего API в формате OpenAPI. Создадим файл spec.yml, который будет содержать описание наших маршрутов:

openapi: 3.0.0 info:   description: "API"   version: "1.0.0"   title: "API" paths:    /mock:     get:       tags:         - Mock       operationId: mock       parameters:         - name: id           in: query           required: true           schema:             type: integer       responses:         '200':           description: "Successful response"           content:             application/json:               schema:                 $ref: '#/components/schemas/MockDataResponse'      post:       tags:         - Mock       operationId: add_mock       requestBody:         required: true         content:           application/json:             schema:               $ref: '#/components/schemas/CreateMockData'       responses:         '200':           description: "Successful response"           content:             application/json:               schema:                 $ref: '#/components/schemas/ResponseCreateMock'  components:   schemas:     MockDataResponse:       type: object       properties:         id:           type: integer         info:           type: string      CreateMockData:       type: object       properties:         info:           type: string      ResponseCreateMock:       type: object       properties:         id:           type: integer 

Как видно из примера, наша документация содержит два основных запроса: GET и POST. Мы также описали объекты, которые эти запросы принимают и возвращают.

Теперь, когда документация готова, мы можем перейти к процессу генерации исходного кода с помощью OpenAPI Generator.

OpenAPI Generator — это инструмент, предназначенный для автоматической генерации кода на основе спецификаций в формате OpenAPI. Он поддерживает множество языков программирования и фреймворков, включая Python, Java, JavaScript, Ruby и многие другие. С полным списком поддерживаемых языков и фреймворков можно ознакомиться здесь.

Установить OpenAPI Generator можно разными способами, но для удобства мы будем использовать Docker. Для этого создадим скрипт generate-api.sh, который автоматизирует процесс генерации:

#!/bin/sh  set -e ROOT=$(dirname $0) cd "$ROOT"  sudo rm -Rf ./endpoints/apis ./endpoints/models ./endpoints/router_init.py mkdir -p "$ROOT/endpoints"  sudo rm -Rf ./openapi-generator-output docker run --rm -v "${PWD}":/app openapitools/openapi-generator-cli:latest-release generate  \     -i /app/spec.yml  -g python-fastapi   -o /app/openapi-generator-output \     --additional-properties=packageName=endpoints --additional-properties=fastapiImplementationPackage=endpoints 

Теперь разберем ключевые аргументы, которые мы используем в команде generate:

  • i указывает на файл с документацией (в нашем случае — spec.yml).

  • g определяет генератор для конкретного фреймворка или языка. Например, чтобы сгенерировать код для приложения на Flask, указываем python-flask, но в нашем случае используется python-fastapi.

  • o задаёт директорию, в которую будет помещен сгенерированный код.

  • -additional-properties=packageName задаёт имя пакета для сгенерированного кода.

  • -additional-properties=fastapiImplementationPackage указывает, где будет находиться реализация FastAPI.

Этот скрипт создаст новую папку openapi-generator-output с множеством файлов. Давайте посмотрим что он создал.

Обзор сгенерированных файлов

После генерации в папке появляется несколько конфигурационных файлов, но основное внимание стоит уделить папке src, так как именно в ней содержится код нашего приложения. Вот краткий обзор её структуры:

  • main.py — файл, содержащий код для запуска приложения FastAPI.

  • security_api.py — здесь описана логика проверки доступа к маршрутам.

  • /api/ — папка с маршрутами API и логикой каждого из них:

    • _api.py — файлы, которые инициализируют маршруты и проверяют, что методы были реализованы.

    • _api_base.py — базовые классы, от которых можно наследоваться для реализации маршрутов.

Также в папке /models/ находятся файлы с классами Pydantic, которые отвечают за валидацию и сериализацию данных.

На этом этапе у нас уже есть сгенерированный код, и его можно использовать в проекте. Чтобы приложение FastAPI нашло все необходимые классы и методы, достаточно создать файл с именем, которое было указано в параметре:

bash --additional-properties=fastapiImplementationPackage 

Этот файл будет содержать бизнес логику всех маршрутов, наследующихся от базовых классов *_api_base.py.

Однако, при генерации OpenAPI Generator создает много лишних файлов, которые не всегда нужны. Поэтому давайте немного изменим наш скрипт generate-api.sh, чтобы убрать ненужные файлы и оставить только то, что нужно:

#!/bin/sh  set -e ROOT=$(dirname $0) cd "$ROOT"  sudo rm -Rf ./endpoints/apis ./endpoints/models ./endpoints/router_init.py mkdir -p "$ROOT/endpoints"  sudo rm -Rf ./openapi-generator-output docker run --rm -v "${PWD}":/app openapitools/openapi-generator-cli:latest-release generate  \     -i /app/spec.yml  -g python-fastapi   -o /app/openapi-generator-output \     --additional-properties=packageName=endpoints --additional-properties=fastapiImplementationPackage=endpoints  sudo chown "$USER":"$USER" -R openapi-generator-output rm -Rf endpoints/apis endpoints/models mv openapi-generator-output/src/endpoints/apis endpoints/ mv openapi-generator-output/src/endpoints/models endpoints/ mv openapi-generator-output/src/endpoints/main.py endpoints/router_init.py rm -Rf openapi-generator-output 

Теперь все нужные файлы находятся в папке endpoints, что делает структуру проекта более чистой и организованной

Как добавить бизнес-логику?

Теперь, когда у нас есть сгенерированные маршруты, давайте разберемся, как добавить бизнес-логику в наше приложение FastAPI. Для этого заглянем в папку /endpoints/apis, где находятся основные файлы с маршрутизаторами.

Пример содержимого /enpoints/apis

В файле mock_api.py мы видим следующее:

router = APIRouter()  ns_pkg = endpoints for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."):     importlib.import_module(name)  @router.post(     "/mock",     responses={         200: {"model": ResponseCreateMock, "description": "Successful response"},     },     tags=["Mock"],     response_model_by_alias=True, ) async def add_mock(     create_mock_data: CreateMockData = Body(None, description=""), ) -> ResponseCreateMock:     if not BaseMockApi.subclasses:         raise HTTPException(status_code=500, detail="Not implemented")     return await BaseMockApi.subclasses[0]().add_mock(create_mock_data)  @router.get(     "/mock",     responses={         200: {"model": MockDataResponse, "description": "Successful response"},     },     tags=["Mock"],     response_model_by_alias=True, ) async def mock(     id: int = Query(None, description="", alias="id"), ) -> MockDataResponse:     if not BaseMockApi.subclasses:         raise HTTPException(status_code=500, detail="Not implemented")     return await BaseMockApi.subclasses[0]().mock(id)  

В файле mock_api.py создаётся роут для fastAPI и проверяется, что наш метод кто то реализует, а так же динамически импортирует все подмодули из переменой ns_pkg (содержит имя папки которое передали в --additional-properties=fastapiImplementationPackage).

В файле mock_api_base.py реализована логика, которая автоматически сохраняет всех наследников класса BaseMockApi в массив и содержит методы, которые нам надо реализовать:

class BaseMockApi:     subclasses: ClassVar[Tuple] = ()      def __init_subclass__(cls, **kwargs):         super().__init_subclass__(**kwargs)         BaseMockApi.subclasses = BaseMockApi.subclasses + (cls,)     async def add_mock(         self,         create_mock_data: CreateMockData,     ) -> ResponseCreateMock:         ...      async def mock(         self,         id: int,     ) -> MockDataResponse:         ... 

Теперь когда мы разобрались в том , что мы сгенерировали, нам не составит труда сделать реализацию наших роутов. Для этого в папке /endpoints создадим файл mock_impl.py и напишем простейшую реализацию.

class MockImpl(BaseMockApi):     data = [MockDataResponse(id=1, info="hello world!"), MockDataResponse(id=2, info="bye world!")]      async def add_mock(self, create_mock_data: CreateMockData) -> ResponseCreateMock:         new_id = max(mock.id for mock in self.data) + 1  #         new_mock = MockDataResponse(id=new_id, info=create_mock_data.info)         self.data.append(new_mock)         return ResponseCreateMock(id=new_id)      async def mock(self, id: int) -> MockDataResponse:         for mock in self.data:             if mock.id == id:                 return mock         raise ValueError("Data with given ID not found.") 

Создадим файл main.py для старта приложения. Если у вас есть необходимость добавить какую-то логику перед запуском сервера, вы можете сделать это в этом файле:

from endpoints.router_init import app 

Теперь запустим сервис командой:

uvicorn main:app --host 0.0.0.0 --port 8080 

Проверим работу сервиса, отправив в него запрос:

curl --location 'http://localhost:8080/mock?id=1' {"id":1,"info":"hello world!"} 

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

Заключение

Использование Swagger и OpenAPI Generator значительно упрощает разработку API, сокращает количество рутинной работы и улучшает качество документации. Этот подход позволяет быстро перейти от описания API к готовому коду, что экономит время и силы. Однако на данный момент не все возможности OpenAPI поддерживаются для генерации кода под Flask и FastAPI. Тем не менее, OpenAPI Generator активно развивается, и эти ограничения могут быть устранены в будущем.

Исходный код


ссылка на оригинал статьи https://habr.com/ru/articles/843950/


Комментарии

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

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