Рубрики
Без рубрики

Реализация простого отдыха API с использованием Openapi, Flask & Connexions

Restful API очень популярны в данный момент, и Python – отличный язык для разработки веб-API с … Теги с колбой, Openapi, Python, Connexion.

RESTFLE API очень популярны в данный момент, и Python – отличный язык для разработки веб-API. В этой статье мы перейдем за документацию первого подхода к созданию API. Мы будем использовать колбу, CWARGER CODE-GEN (OPRAPI) и CONNEXIONS. Я пойду на первый подход API/документацию, чтобы построить спокойную API в Python. Что постарается минимизировать различия между тем, что определено в спецификации API и самой фактической логике API.

Одна из главных проблем, которые вы найдете с использованием Openapi, это то, что каждый раз, когда вы обновляете API, вы должны обновить документацию или файл Openapi YAML/JSON. Теперь, что произойдет, если вы забудете? Теперь ваш API отличается от того, что задокументировано, что может быть настоящей болью для ваших пользователей. Целью этого подхода является то, что вы сначала обновляете свой файл спецификации.

Инструменты/библиотеки

Давайте очень быстро перейдем на инструменты и библиотеки, которые мы будем использовать.

Openapi

Спецификация Openapi или Specification (OAS) определяет стандартный язык языка агностического подхода к разработке реставральных API, которые являются читаемыми человеками и машинами.

Свал

Набор инструментов с открытым исходным кодом, построенным вокруг OAS, который помогает поддержать разработку, в том числе:

  • Редактор Swagg: Редактор на основе браузера, где вы можете написать (и просмотреть) Specs Openapi.
  • Swagger Ui: Оказывает OAS в качестве интерактивной документации API (также можно увидеть в редакторе Swagger).
  • CWAGGER CODEGEN – генерирует Server STUBS и клиентские библиотеки из Spec openapi.

Связи

Это библиотека Python, которая «автоматически» обрабатывает HTTP-запросы на основе вашего OAS. Он действует как простая обертка вокруг колба, уменьшающая код котеля, который вы должны написать, а также писать. Таким образом, мы все еще имеем доступ ко всем функциям, которые мы бы при разработке обычной Flask Web API.

ЗАМЕТКА: На момент написания статьи эта статья OS3 поддержка только что вышла на кодеген. Таким образом, эта статья написана с использованием OAS2. Однако все в этой статье должно быть применимо к OAS2 и AOS3.

API.

Теперь на самом деле развитие наших API.

Структура проекта

В этой статье наш код будет использовать следующую структуру.

test-api/
├── openapi/
├── src/
|   └── test_api
|   |  ├── wsgi.py
|   |  ├──__init__.py
|   |  ├── core/
|   |  └── web/
└── setup.py

Определите спецификацию

Первое, что мы делаем, это определить нашу Оа. Мы будем использовать YAML, чтобы сделать это, потому что я думаю, что гораздо проще читать, и почти все спецификации, которые вы видите, будут написаны в YAML (не JSON). Однако вы можете написать спецификацию в JSON, если вы так пожелаете. Есть несколько инструментов, которые могут сделать это немного легче.

Мы можем использовать Онлайн редактор Swagger , что позволяет нам редактировать OAS, и вы можете увидеть OAS в качестве интерактивного документа (половина экрана для редактора и половины для интерактивного документа). Вы также можете запустить редактор локально, как Контейнер докера

Примечание Если вы используете редактор для генерации моделей (с помощью Swagger-Codegngna), он делает вызов API для Удаленный сервер. Запустите Swagger-Codegen вручную, чтобы создавать модели локально, если вы используете Это для работы и конфиденциальность имеет значение.

Мой предпочтительный способ написания OAS использует VSCode с Swagger Viewer Плагин, который позволяет писать OAS и предварительно просмотреть интерактивный документ одновременно. Я предпочитаю этот подход, потому что у меня есть все мои установки плагинов (цветовая схема, привязки VIM и т. Д.).

Теперь мы должны определить нашу спецификацию. Мы будем использовать OAS версию 2, потому что Swarger-Codegen в данный момент не может генерировать модели для колбы для OAS версии 3. Теперь я создал очень простую спецификацию для воображаемого домашнего животного магазина.

# openapi/specification.yml
swagger: "2.0"
info:
  version: "1.0.0"
  title: "Pet Store"
basePath: "/api/v1"
tags:
  - name: "pet"
schemes:
  - "https"
consumes:
  - "application/json"
produces:
  - "application/json"
paths:
  /pet/{pet_id}:
    get:
      tags:
        - "pet"
      summary: "Get a pet in the store"
      operationId: "get_pet"
      parameters:
        - name: "pet_id"
          in: "path"
          description: "The id of the pet to retrieve"
          required: true
          type: "string"
      responses:
        200:
          description: "Successfully retrived pet"
          schema:
            $ref: "#/definitions/Pet"
        404:
          description: "Pet doesn't exist"
      x-swagger-router-controller: "test_api.web.controllers.pets_controller"
    delete:
      tags:
        - "pet"
      summary: "Remove a pet in the store"
      operationId: "remove_pet"
      parameters:
        - name: "pet_id"
          in: "path"
          description: "The id of the pet to remove from the store"
          required: true
          type: "string"
      responses:
        202:
          description: "Successfully deleted pet"
        404:
          description: "Pet doesn't exist"
      x-swagger-router-controller: "test_api.web.controllers.pets_controller"

Спецификация определяет несколько конечных точек для нашего API. По сути, я определил одну конечную точку для каждого из главных глаголов CRUD (Get, Post, Put и Delete). Некоторые вещи следует отметить: apport_id будет название функции в нашем коде Python. В производстве вы также должны смотреть на использование OAUTH2 Для обеспечения вашего API это также может быть определено в пределах спецификации.

Примечание дополнительное поле X-Swagger-Router-Controller очень важно. Используется Connexion к Карта какого модуля (и функции) отправлять запросы на. Например, Получить запрос Отправить на /API/V1/Домашние животные , пойду на test_api.web.controlllers.pets_controller и функция называется get_pet. ( appress_id ) Так выглядит как test_api.web.controllers.pets_controller: get_pet Отказ Что означает, что мы называем функцию В папке src/test_api/web/controllers/pets_controller Мы называем get_pet функция.

Сервер заглушки

Теперь мы хотим создать некоторые серверные заглушки из этой спецификации, мы можем сделать это либо, используя Codegen Инструмент или в редактор Мы можем пойти в Создать сервер> Python-Flask Отказ Это загрузит zip-файл, после того как вы распадаете его. Мы хотим скопировать Контроллеры, модели, Encoder.py, __init__.py и util.py Файлы в Веб папка. Модели – это классы объектов, которые мы ожидаем в качестве ввода и вывода, такие как Домашнее животное класс. Контроллеры содержат реальную логику веб-сервера. Существует одна функция для каждой конечной точки (и метода CRUD), который мы определены выше, есть также один файл для каждого тега, который мы определены. В этом примере у нас есть только один файл контроллера, потому что у нас есть только Тег называется питомца. Затем в контроллере у нас есть 4 функции (названные в честь apploet_id ).

Мы должны внести некоторые изменения в сгенерированные файлы Codegen. Импорт будет неправ, когда мы переместим файлы. Мы должны изменить их из swagger_server Отказ Так, например Контроллеры/Pet_controller.py и Модели/Pets.py станет:

#pet_controller.py
from ..models.pet import Pet  # noqa: E501
from ..models.pets import Pets  # noqa: E501
from .. import util
#pets.py
from .base_model_ import Model
from .pet import Pet  # noqa: F401,E501
from .. import util

В этом случае я использую относительный импорт, но вы также можете использовать абсолютный импорт. Например Находятся models.patch_request будет стать test_api.models.patch_request. . Это все личные предпочтения. Эта статья Учитывается более подробно по вопросу.

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

Поэтому теперь мы создали некоторые модели и контроллеры из нашей спецификации Operapi, мы можем написать логику для нашего приложения. Я обычно Напишите всю мою основную логику в папке под названием Core который является родным братом test_api Отказ Затем я импортирую модули в контроллеры. Это Добавляет хороший слой абстракции, скажем, завтра вы хотели превратиться в CLI, мы можем сохранить Core папка и удалить Веб Папка и добавить библиотеку CLI, как Нажмите Отказ Это связано с минимальным изменением кода.

Примечание Некоторое импорт может быть ненужным, вы можете использовать Linter (например, Flask8 ), чтобы помочь вам удалить их из моделей .

Основная логика

Я создал файл под названием PETS.PY в Core Отказ В этом примере мы просто пишем и читаем из файла JSON. Это не лучший код, который я написал, но должно быть достаточно, чтобы показать, что мы пытаемся достичь. На самом деле эти данные могут быть сохранены в базе данных, но я не хочу переоценить этот пример. Насколько вам обеспокоенные данные хранятся и извлекаются из файла, как если бы это была база данных.

...

def add_pet(pet):
    pets = read_from_file()
    ids = pets.keys()
    new_id = int(ids[-1]) + 1
    pets[new_id] = {"name": pet.name, "breed": pet.breed, "price": pet.price}
    write_to_file(pets)

...

Контроллеры

Теперь у нас есть наша основная логика, давайте смотрим на то, как мы взаимодействуем с ним в наших контроллерах, сначала Импорт Test_api.Core Импорт домашних животных Импортируйте наш новый файл в контроллеры ( Pet_Controller ).

Тогда давайте посмотрим на get_pet.

def get_pet(pet_id):  # noqa: E501
    """Get a pet in the store

     # noqa: E501

    :param pet_id: The id of the pet to retrieve
    :type pet_id: str

    :rtype: Pet
    """
    try:
        pet = pets.get_pet(pet_id)
        response = Pet(id=pet.id, breed=pet.breed, name=pet.name, price=pet.price), 200
    except KeyError:
        response = {}, 404

    return response

Как вы можете видеть, мы называем нашими get_pet () Функция из нашего Core.Pets. модуль. Тогда, если домашние животные существуют, мы включаем DICT, который возвращается, в объект Python Class Домашнее животное Согласно rtype. Мы определили в нашем Оа. Connexion будет обрабатывать преобразование этого объекта в JSON. Еще одна вещь, которую мы делаем, это если KeyError Исключение было брошено, что должно означать, что у нас нет домашнего животного с этим идентификатором в зоомагазине. Скажем, у нас есть следующие

{
  "1": {
    "name": "ginger",
    "breed": "bengal",
    "price": 100
  },
  "2": {
    "name": "sam",
    "breed": "husky",
    "price": 10
  },
  "3": {
    "name": "guido",
    "breed": "python",
    "price": 518
  }
}

Если мы попытаемся получить питомца ID 4, Python бросит keyError, говоря, что это не существует (когда мы загружаем файл json, который мы преобразуем в Dict). Таким образом, в этом случае согласно нашему ОАМ мы хотим вернуть 404 питомца не существует.

responses:
  200:
    description: "Successfully retrived pet"
      schema:
      $ref: "#/definitions/Pet"
  404:
    description: "Pet doesn't exist"

Одной из очень важно отметить, что когда мы получим HTTP-запрос с JSON, скажем, для add_pet () Функция Connexion преобразует это в объект Python для нас, и когда мы возвращаем объект Python, он преобразует этот объект Python в JSON. Таким образом, в наших контроллерах и основной логике нам на самом деле не нужно взаимодействовать с JSON вообще. Это все абстрагировано с библиотекой связи. Нам также не нужно использовать CWAGGER CODEGEN для генерации моделей и контроллеров, которые мы могли бы сделать себя, Connexions может работать на своей собственности без них.

Давайте посмотрим на пример этого.

# pets_controller.py
def add_pet(body):  # noqa: E501
    """Add a new pet to the store

     # noqa: E501

    :param body: Pet to add to the store
    :type body: dict | bytes

    :rtype: None
    """
    if connexion.request.is_json:
        body = Pet.from_dict(connexion.request.get_json())  # noqa: E501

    pets.add_pet(body)
    return {}, 201

Переменная тела будет объектом Python Class Pet. Затем мы можем передать это как аргумент нашим другим add_pet Функция в нашей основной папке. Как вы можете увидеть, что мы доступаем к атрибутам, потому что это объект не Dict I.e. Домашние животные [«Имя»] против имя питомца .

# pets.py
def add_pet(pet):
    pets = read_from_file()
    ids = pets.keys()
    new_id = int(ids[-1]) + 1
    pets[new_id] = {"name": pet.name, "breed": pet.breed, "price": pet.price}
    write_to_file(pets)

Swagger Codegen VS Connexion

Таким образом, Connexion делает все маршрутизацию и проверку для нас, но CWAGGER Codegen – это то, что преобразует наш вход и вывод в классы Python. Коннектионы только имеют дело с JSON, он преобразует JSON в свой эквивалентный объект Python, такой как списки, строки и словарь. Swagger Codegen примет этот вклад (словарь) и преобразует это в класс Python. Один пример этого в add_pet Функция в PETS_CONTROLLER файл. Это преобразует наш словарь в Домашнее животное объект (как показано ниже). Так что вместо доступа к данным с использованием нормального словаря Тело [«ID»] Теперь мы можем использовать Body.id Отказ body.from_dict (connexion.request.get_json ()) # noqa: e501

Для Codegen для преобразования наших объектов Python обратно в словарь, так что Connexion может затем преобразовать это в JSON, поэтому ответит назад, мы используем json Encoder, который Codegen предоставляет нам ( test_api.web.encoder ). Чтобы использовать все, что нам нужно добавить, это установить его в качестве нашего датчика по умолчанию для нашего приложения Flask flask_app.json_encoder. Jsonencoder Обычно это делается в настройке приложения (показано ниже).

Запустить сервер

Теперь, когда у нас есть наш код, как мы фактически запускаем наше веб-приложение, чтобы мы могли ее проверить. Для этого мы создадим файл, который, в свою очередь, создаст наше приложение Connexion/Flask и запустить сервер, называемый Run.py внутри нашего test_api папка.

import os

import connexion

from .web import encoder


def create_app():
    abs_file_path = os.path.abspath(os.path.dirname(__file__))
    openapi_path = os.path.join(abs_file_path, "../", "../", "openapi")
    app = connexion.FlaskApp(
        __name__, specification_dir=openapi_path, options={"swagger_ui": False, "serve_spec": False}
    )
    app.add_api("specification.yml", strict_validation=True)
    flask_app = app.app
    flask_app.json_encoder = encoder.JSONEncoder

    return flask_app

Вы можете запустить приложение, как обычное приложение Flask из рута проекта (работает из папки, где Openapi/ и SRC/ существовать.)

FLASK_APP=./src/test_api/run.py FLASK_DEBUG=1 flask run

Пример проекта

Связанные с этой статьей есть пример проекта, который вы можете взглянуть, чтобы запустить его выполнить следующее. Вуаля Мы создали сервисную службу с помощью Connexion и Openapi.

git clone https://gitlab.com/hmajid2301/medium.git
cd medium/13.\ REST\ API\ using\ OpenAPI\,\ Flask\ \&\ Connexions/source_code/test-api
virtualenv .venv
source .venv/bin/activate
pip install -r requirements.txt
FLASK_APP=test_api.wsgi:app FLASK_DEBUG=1 flask run

Последние мысли

Поэтому, как вы можете видеть, мы построили веб-API с помощью Connexion и Flask, где весь наш код генерируется на основе нашего OAS. Итак, теперь мы уверены, что наша документация API точна. Нам также удалось уменьшить часть котельной с помощью колбы, соединения Connexions, какие функции должны быть вызваны в зависимости от работы CRUD (создайте обновление чтения), и конечные точки определены в OAS.

Приложение

Оригинал: “https://dev.to/hmajid2301/implementing-a-simple-rest-api-using-openapi-flask-connexions-28kk”